Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Builtin FSMonitor Part 1 #1040

Closed
40 changes: 40 additions & 0 deletions Documentation/technical/api-trace2.txt
Expand Up @@ -613,6 +613,46 @@ stopping after the waitpid() and includes OS process creation overhead).
So this time will be slightly larger than the atexit time reported by
the child process itself.

`"child_ready"`::
This event is generated after the current process has started
a background process and released all handles to it.
+
------------
{
"event":"child_ready",
...
"child_id":2,
"pid":14708, # child PID
"ready":"ready", # child ready state
"t_rel":0.110605 # observed run-time of child process
}
------------
+
Note that the session-id of the child process is not available to
the current/spawning process, so the child's PID is reported here as
a hint for post-processing. (But it is only a hint because the child
process may be a shell script which doesn't have a session-id.)
+
This event is generated after the child is started in the background
and given a little time to boot up and start working. If the child
startups normally and while the parent is still waiting, the "ready"
field will have the value "ready".
If the child is too slow to start and the parent times out, the field
will have the value "timeout".
If the child starts but the parent is unable to probe it, the field
will have the value "error".
+
After the parent process emits this event, it will release all of its
handles to the child process and treat the child as a background
daemon. So even if the child does eventually finish booting up,
the parent will not emit an updated event.
+
Note that the `t_rel` field contains the observed run time in seconds
when the parent released the child process into the background.
The child is assumed to be a long-running daemon process and may
outlive the parent process. So the parent's child event times should
not be compared to the child's atexit times.

`"exec"`::
This event is generated before git attempts to `exec()`
another command rather than starting a child process.
Expand Down
14 changes: 9 additions & 5 deletions compat/simple-ipc/ipc-unix-socket.c
Expand Up @@ -168,15 +168,16 @@ void ipc_client_close_connection(struct ipc_client_connection *connection)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Eric Sunshine wrote (reply to this):

On Wed, Sep 15, 2021 at 4:36 PM Jeff Hostetler via GitGitGadget
<gitgitgadget@gmail.com> wrote:
> Add `command_len` argument to the Simple IPC API.
>
> In my original Simple IPC API, I assumed that the request
> would always be a null-terminated string of text characters.
> The command arg was just a `const char *`.
>
> I found a caller that would like to pass a binary command
> to the daemon, so I want to ammend the Simple IPC API to

s/ammend/amend/

> take `const char *command, size_t command_len` and pass
> that to the daemon.  (Really, the first arg should just be
> a `void *` or `const unsigned byte *` to make that clearer.)

The reader is left wondering why you didn't also change it to `const
void *` (or one of the other choices) while at it.

> Note, the response side has always been a `struct strbuf`
> which includes the buffer and length, so we already support
> returning a binary answer.  (Yes, it feels a little weird
> returning a binary buffer in a `strbuf`, but it works.)
>
> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Jeff Hostetler wrote (reply to this):



On 9/15/21 4:43 PM, Eric Sunshine wrote:
> On Wed, Sep 15, 2021 at 4:36 PM Jeff Hostetler via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
>> Add `command_len` argument to the Simple IPC API.
>>
>> In my original Simple IPC API, I assumed that the request
>> would always be a null-terminated string of text characters.
>> The command arg was just a `const char *`.
>>
>> I found a caller that would like to pass a binary command
>> to the daemon, so I want to ammend the Simple IPC API to
> 
> s/ammend/amend/
> 
>> take `const char *command, size_t command_len` and pass
>> that to the daemon.  (Really, the first arg should just be
>> a `void *` or `const unsigned byte *` to make that clearer.)
> 
> The reader is left wondering why you didn't also change it to `const
> void *` (or one of the other choices) while at it.
> 

The simple ipc layer just passes the buffer to the pkt-line layer
and it takes a "const char *", so to avoid confusion I just left
the type is it was.  If later we want to fix pkt-line, we can
investigate passing a "const unsigned byte *" value down the
call chain, but that is more than I want to do right now.

>> Note, the response side has always been a `struct strbuf`
>> which includes the buffer and length, so we already support
>> returning a binary answer.  (Yes, it feels a little weird
>> returning a binary buffer in a `strbuf`, but it works.)
>>
>> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>

int ipc_client_send_command_to_connection(
struct ipc_client_connection *connection,
const char *message, struct strbuf *answer)
const char *message, size_t message_len,
struct strbuf *answer)
{
int ret = 0;

strbuf_setlen(answer, 0);

trace2_region_enter("ipc-client", "send-command", NULL);

if (write_packetized_from_buf_no_flush(message, strlen(message),
if (write_packetized_from_buf_no_flush(message, message_len,
connection->fd) < 0 ||
packet_flush_gently(connection->fd) < 0) {
ret = error(_("could not send IPC command"));
Expand All @@ -197,7 +198,8 @@ int ipc_client_send_command_to_connection(

int ipc_client_send_command(const char *path,
const struct ipc_client_connect_options *options,
const char *message, struct strbuf *answer)
const char *message, size_t message_len,
struct strbuf *answer)
{
int ret = -1;
enum ipc_active_state state;
Expand All @@ -208,7 +210,9 @@ int ipc_client_send_command(const char *path,
if (state != IPC_STATE__LISTENING)
return ret;

ret = ipc_client_send_command_to_connection(connection, message, answer);
ret = ipc_client_send_command_to_connection(connection,
message, message_len,
answer);

ipc_client_close_connection(connection);

Expand Down Expand Up @@ -503,7 +507,7 @@ static int worker_thread__do_io(
if (ret >= 0) {
ret = worker_thread_data->server_data->application_cb(
worker_thread_data->server_data->application_data,
buf.buf, do_io_reply_callback, &reply_data);
buf.buf, buf.len, do_io_reply_callback, &reply_data);

packet_flush_gently(reply_data.fd);
}
Expand Down
179 changes: 162 additions & 17 deletions compat/simple-ipc/ipc-win32.c
Expand Up @@ -3,6 +3,8 @@
#include "strbuf.h"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Ævar Arnfjörð Bjarmason wrote (reply to this):


On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote:

> +struct my_sa_data
> +{
> +	PSID pEveryoneSID;
> +	PACL pACL;
> +	PSECURITY_DESCRIPTOR pSD;
> +	LPSECURITY_ATTRIBUTES lpSA;
> +};
> +
> +static void init_sa(struct my_sa_data *d)
> +{
> +	memset(d, 0, sizeof(*d));
> +}
> +
> +static void release_sa(struct my_sa_data *d)
> +{
> +	if (d->pEveryoneSID)
> +		FreeSid(d->pEveryoneSID);
> +	if (d->pACL)
> +		LocalFree(d->pACL);
> +	if (d->pSD)
> +		LocalFree(d->pSD);
> +	if (d->lpSA)
> +		LocalFree(d->lpSA);
> +
> +	memset(d, 0, sizeof(*d));
> +}

[...]

> +fail:
> +	release_sa(d);
> +	return NULL;
> +}
> +
>  static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
>  {
>  	HANDLE hPipe;
>  	DWORD dwOpenMode, dwPipeMode;
> -	LPSECURITY_ATTRIBUTES lpsa = NULL;
> +	struct my_sa_data my_sa_data;
> +
> +	init_sa(&my_sa_data);
>  

[...]

>  	hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
> -				 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
> +				 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
> +				 my_sa_data.lpSA);
> +
> +	release_sa(&my_sa_data);
>  
>  	return hPipe;
>  }

Just a nit on the init pattern, since we always allocate this on the
stack (this as all the relevant "struct my_sa_data") I'd have thought to
see:

    #define INIT_MY_SA_DATA { 0 }
    [...]
    struct my_sa_data my_sa_data = INIT_MY_SA_DATA;

Which gets rid of the need for an init_sa() function.

Also having the release_sa() do a memset() is a bit odd, usually we have
a reset*() function do that if the intent is to re-use, but it doesn't
appear to be in this case, and we don't return this data anywhere, do
we?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Jeff Hostetler wrote (reply to this):



On 9/16/21 1:47 AM, Ævar Arnfjörð Bjarmason wrote:
> 
> On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote:
> 
>> +struct my_sa_data
>> +{
>> +	PSID pEveryoneSID;
>> +	PACL pACL;
>> +	PSECURITY_DESCRIPTOR pSD;
>> +	LPSECURITY_ATTRIBUTES lpSA;
>> +};
>> +
>> +static void init_sa(struct my_sa_data *d)
>> +{
>> +	memset(d, 0, sizeof(*d));
>> +}
>> +
>> +static void release_sa(struct my_sa_data *d)
>> +{
>> +	if (d->pEveryoneSID)
>> +		FreeSid(d->pEveryoneSID);
>> +	if (d->pACL)
>> +		LocalFree(d->pACL);
>> +	if (d->pSD)
>> +		LocalFree(d->pSD);
>> +	if (d->lpSA)
>> +		LocalFree(d->lpSA);
>> +
>> +	memset(d, 0, sizeof(*d));
>> +}
> 
> [...]
> 
>> +fail:
>> +	release_sa(d);
>> +	return NULL;
>> +}
>> +
>>   static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
>>   {
>>   	HANDLE hPipe;
>>   	DWORD dwOpenMode, dwPipeMode;
>> -	LPSECURITY_ATTRIBUTES lpsa = NULL;
>> +	struct my_sa_data my_sa_data;
>> +
>> +	init_sa(&my_sa_data);
>>   
> 
> [...]
> 
>>   	hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
>> -				 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
>> +				 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
>> +				 my_sa_data.lpSA);
>> +
>> +	release_sa(&my_sa_data);
>>   
>>   	return hPipe;
>>   }
> 
> Just a nit on the init pattern, since we always allocate this on the
> stack (this as all the relevant "struct my_sa_data") I'd have thought to
> see:
> 
>      #define INIT_MY_SA_DATA { 0 }
>      [...]
>      struct my_sa_data my_sa_data = INIT_MY_SA_DATA;
> 
> Which gets rid of the need for an init_sa() function.

The current "my_sa_data" is just a set of 4 pointers, so yes your
INIT_MY_SA_DATA macro and my init_sa function are equivalent.
(And assuming that mine and memset are inlined by the compiler, they
are both probably exactly equivalent.)   So it really doesn't matter
one way or the other.

> 
> Also having the release_sa() do a memset() is a bit odd, usually we have
> a reset*() function do that if the intent is to re-use, but it doesn't
> appear to be in this case, and we don't return this data anywhere, do
> we?
> 

I use the release_sa() function inside my "fail:" label in get_sa()
to cleanup if any of the component parts of the SA cannot be created.
So the return value of get_sa() is either fully constructed or contains
NULL pointers.

(The docs are jargon heavy and little obscure and circular and it
is not at all clear if/when we might fail to create some of these
sub-structures.)

So I allow the SA failure to be silently ignored and we create the named
pipe without an ACL.  Normally, this is fine since the daemon usually
isn't elevated.

In create_new_pipe we always call release_sa to free the pointers if
necessary.  (So in case of a failure in get_sa, we won't double free
the pointers when create_new_pipe calls release_sa.)

Another way to think of it is that in release_sa, I'd like to have
FREE_AND_NULL() variants for FreeSid() and LocalFree(), but a simple
memset is sufficient.


Jeff

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Ævar Arnfjörð Bjarmason wrote (reply to this):


On Fri, Sep 17 2021, Jeff Hostetler wrote:

> On 9/16/21 1:47 AM, Ævar Arnfjörð Bjarmason wrote:
>> On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote:
>> 
>>> +struct my_sa_data
>>> +{
>>> +	PSID pEveryoneSID;
>>> +	PACL pACL;
>>> +	PSECURITY_DESCRIPTOR pSD;
>>> +	LPSECURITY_ATTRIBUTES lpSA;
>>> +};
>>> +
>>> +static void init_sa(struct my_sa_data *d)
>>> +{
>>> +	memset(d, 0, sizeof(*d));
>>> +}
>>> +
>>> +static void release_sa(struct my_sa_data *d)
>>> +{
>>> +	if (d->pEveryoneSID)
>>> +		FreeSid(d->pEveryoneSID);
>>> +	if (d->pACL)
>>> +		LocalFree(d->pACL);
>>> +	if (d->pSD)
>>> +		LocalFree(d->pSD);
>>> +	if (d->lpSA)
>>> +		LocalFree(d->lpSA);
>>> +
>>> +	memset(d, 0, sizeof(*d));
>>> +}
>> [...]
>> 
>>> +fail:
>>> +	release_sa(d);
>>> +	return NULL;
>>> +}
>>> +
>>>   static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
>>>   {
>>>   	HANDLE hPipe;
>>>   	DWORD dwOpenMode, dwPipeMode;
>>> -	LPSECURITY_ATTRIBUTES lpsa = NULL;
>>> +	struct my_sa_data my_sa_data;
>>> +
>>> +	init_sa(&my_sa_data);
>>>   
>> [...]
>> 
>>>   	hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
>>> -				 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
>>> +				 PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
>>> +				 my_sa_data.lpSA);
>>> +
>>> +	release_sa(&my_sa_data);
>>>     	return hPipe;
>>>   }
>> Just a nit on the init pattern, since we always allocate this on the
>> stack (this as all the relevant "struct my_sa_data") I'd have thought to
>> see:
>>      #define INIT_MY_SA_DATA { 0 }
>>      [...]
>>      struct my_sa_data my_sa_data = INIT_MY_SA_DATA;
>> Which gets rid of the need for an init_sa() function.
>
> The current "my_sa_data" is just a set of 4 pointers, so yes your
> INIT_MY_SA_DATA macro and my init_sa function are equivalent.
> (And assuming that mine and memset are inlined by the compiler, they
> are both probably exactly equivalent.)   So it really doesn't matter
> one way or the other.

Yes it's the same to the compiler, see 5726a6b4012 (*.c *_init(): define
in terms of corresponding *_INIT macro, 2021-07-01).

But being the same to the compiler != the same to human readers. I was
going for the latter here, i.e. in git.git init with a macro tends to be
used if the init is simple enough to be run like that, and with a
function if it really does need to run some function (and then after
5726a6b4012 the init-via-function-via-macro for things that need init
after malloc() or whatever).

Whereas this one's just on the stack, and doesn't need anything special,
so the nit was about just preferring the simplest construct for the job.

>> Also having the release_sa() do a memset() is a bit odd, usually we
>> have
>> a reset*() function do that if the intent is to re-use, but it doesn't
>> appear to be in this case, and we don't return this data anywhere, do
>> we?
>> 
>
> I use the release_sa() function inside my "fail:" label in get_sa()
> to cleanup if any of the component parts of the SA cannot be created.
> So the return value of get_sa() is either fully constructed or contains
> NULL pointers.
>
> (The docs are jargon heavy and little obscure and circular and it
> is not at all clear if/when we might fail to create some of these
> sub-structures.)
>
> So I allow the SA failure to be silently ignored and we create the named
> pipe without an ACL.  Normally, this is fine since the daemon usually
> isn't elevated.
>
> In create_new_pipe we always call release_sa to free the pointers if
> necessary.  (So in case of a failure in get_sa, we won't double free
> the pointers when create_new_pipe calls release_sa.)
>
> Another way to think of it is that in release_sa, I'd like to have
> FREE_AND_NULL() variants for FreeSid() and LocalFree(), but a simple
> memset is sufficient.

*nod*, I meant if functions are doing that sort of intent-to-reuse we
 usually call them *_reset(), with a *_release() that's just a plain
 free() and forget.

#include "pkt-line.h"
#include "thread-utils.h"
#include "accctrl.h"
#include "aclapi.h"

#ifndef SUPPORTS_SIMPLE_IPC
/*
Expand Down Expand Up @@ -49,6 +51,9 @@ static enum ipc_active_state get_active_state(wchar_t *pipe_path)
if (GetLastError() == ERROR_FILE_NOT_FOUND)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Ævar Arnfjörð Bjarmason wrote (reply to this):


On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote:

> @@ -112,6 +115,11 @@ static enum ipc_active_state connect_to_server(
>  				if (GetLastError() == ERROR_SEM_TIMEOUT)
>  					return IPC_STATE__NOT_LISTENING;
>  
> +				gle = GetLastError();
> +				trace2_data_intmax("ipc-debug", NULL,
> +						   "connect/waitpipe/gle",
> +						   (intmax_t)gle);
> +
>  				return IPC_STATE__OTHER_ERROR;
>  			}
>  

I've never used this Win32 API (or well, any Win32 API) but I'm guessing
that GetLastError() isn't here to check an error in GetLastError() itself.

Earlier in this function added in your 59c7b88198a (simple-ipc: add
win32 implementation, 2021-03-15) we assign to "gle", I'd really expect...:

>  	if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {
> +		gle = GetLastError();
> +		trace2_data_intmax("ipc-debug", NULL,
> +				   "connect/setpipestate/gle",
> +				   (intmax_t)gle);
> +
>  		CloseHandle(hPipe);
>  		return IPC_STATE__OTHER_ERROR;
>  	}
>  
>  	*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
>  	if (*pfd < 0) {
> +		gle = GetLastError();
> +		trace2_data_intmax("ipc-debug", NULL,
> +				   "connect/openosfhandle/gle",
> +				   (intmax_t)gle);
> +
>  		CloseHandle(hPipe);
>  		return IPC_STATE__OTHER_ERROR;
>  	}

...something that looks exactly like this. I.e. as shown by the below
hunk-at-the-end, as it is I'm either missing some subtlety that could
really use explaining. I.e. this reads like:

    int saved_errno = errno;
    if (syscall()) {
        if (errno)
            die("bad");
        saved_errno = errno;
        log_it("...%d", saved_errno);
    }

When surely we want either of:

    int saved_errno = errno;
    if (syscall()) {
        if (errno)
            die("bad");
        log_it("...%d", errno);
    }

Or better yet (and consistent with the rest of your code):

    int saved_errno = errno;
    if (syscall()) {
        saved_errno = errno;
        if (saved_errno)
            die("bad");
        log_it("...%d", saved_errno);
    }

diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c
index 8dc7bda087d..b0c422e4867 100644
--- a/compat/simple-ipc/ipc-win32.c
+++ b/compat/simple-ipc/ipc-win32.c
@@ -109,9 +109,12 @@ static enum ipc_active_state connect_to_server(
 			t_start_ms = (DWORD)(getnanotime() / 1000000);
 
 			if (!WaitNamedPipeW(wpath, timeout_ms)) {
-				if (GetLastError() == ERROR_SEM_TIMEOUT)
+				gle = GetLastError();
+				if (gle == ERROR_SEM_TIMEOUT)
 					return IPC_STATE__NOT_LISTENING;
 
+				/* ...rest of your patch */
+
 				return IPC_STATE__OTHER_ERROR;
 			}
 

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Git mailing list, Jeff Hostetler wrote (reply to this):



On 9/16/21 1:40 AM, Ævar Arnfjörð Bjarmason wrote:
> 
> On Wed, Sep 15 2021, Jeff Hostetler via GitGitGadget wrote:
> 
>> @@ -112,6 +115,11 @@ static enum ipc_active_state connect_to_server(
>>   				if (GetLastError() == ERROR_SEM_TIMEOUT)
>>   					return IPC_STATE__NOT_LISTENING;
>>   
>> +				gle = GetLastError();
>> +				trace2_data_intmax("ipc-debug", NULL,
>> +						   "connect/waitpipe/gle",
>> +						   (intmax_t)gle);
>> +
>>   				return IPC_STATE__OTHER_ERROR;
>>   			}
>>   
>... 
 >
> @@ -109,9 +109,12 @@ static enum ipc_active_state connect_to_server(
>   			t_start_ms = (DWORD)(getnanotime() / 1000000);
>   
>   			if (!WaitNamedPipeW(wpath, timeout_ms)) {
> -				if (GetLastError() == ERROR_SEM_TIMEOUT)
> +				gle = GetLastError();
> +				if (gle == ERROR_SEM_TIMEOUT)
>   					return IPC_STATE__NOT_LISTENING;
>   
> +				/* ...rest of your patch */
> +
>   				return IPC_STATE__OTHER_ERROR;
>   			}
>   
> 

Yeah, I was just trying to minimize the size of the diff
and at the time was considering those debug messages to be
temporary, but I kind of like having them so it makes sense
to clean up a bit as you've indicated.

Thanks
Jeff

return IPC_STATE__PATH_NOT_FOUND;

trace2_data_intmax("ipc-debug", NULL, "getstate/waitpipe/gle",
(intmax_t)GetLastError());

return IPC_STATE__OTHER_ERROR;
}

Expand Down Expand Up @@ -109,9 +114,15 @@ static enum ipc_active_state connect_to_server(
t_start_ms = (DWORD)(getnanotime() / 1000000);

if (!WaitNamedPipeW(wpath, timeout_ms)) {
if (GetLastError() == ERROR_SEM_TIMEOUT)
DWORD gleWait = GetLastError();

if (gleWait == ERROR_SEM_TIMEOUT)
return IPC_STATE__NOT_LISTENING;

trace2_data_intmax("ipc-debug", NULL,
"connect/waitpipe/gle",
(intmax_t)gleWait);

return IPC_STATE__OTHER_ERROR;
}

Expand All @@ -133,17 +144,31 @@ static enum ipc_active_state connect_to_server(
break; /* try again */

default:
trace2_data_intmax("ipc-debug", NULL,
"connect/createfile/gle",
(intmax_t)gle);

return IPC_STATE__OTHER_ERROR;
}
}

if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) {
gle = GetLastError();
trace2_data_intmax("ipc-debug", NULL,
"connect/setpipestate/gle",
(intmax_t)gle);

CloseHandle(hPipe);
return IPC_STATE__OTHER_ERROR;
}

*pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY);
if (*pfd < 0) {
gle = GetLastError();
trace2_data_intmax("ipc-debug", NULL,
"connect/openosfhandle/gle",
(intmax_t)gle);

CloseHandle(hPipe);
return IPC_STATE__OTHER_ERROR;
}
Expand Down Expand Up @@ -208,15 +233,16 @@ void ipc_client_close_connection(struct ipc_client_connection *connection)

int ipc_client_send_command_to_connection(
struct ipc_client_connection *connection,
const char *message, struct strbuf *answer)
const char *message, size_t message_len,
struct strbuf *answer)
{
int ret = 0;

strbuf_setlen(answer, 0);

trace2_region_enter("ipc-client", "send-command", NULL);

if (write_packetized_from_buf_no_flush(message, strlen(message),
if (write_packetized_from_buf_no_flush(message, message_len,
connection->fd) < 0 ||
packet_flush_gently(connection->fd) < 0) {
ret = error(_("could not send IPC command"));
Expand All @@ -239,7 +265,8 @@ int ipc_client_send_command_to_connection(

int ipc_client_send_command(const char *path,
const struct ipc_client_connect_options *options,
const char *message, struct strbuf *response)
const char *message, size_t message_len,
struct strbuf *response)
{
int ret = -1;
enum ipc_active_state state;
Expand All @@ -250,7 +277,9 @@ int ipc_client_send_command(const char *path,
if (state != IPC_STATE__LISTENING)
return ret;

ret = ipc_client_send_command_to_connection(connection, message, response);
ret = ipc_client_send_command_to_connection(connection,
message, message_len,
response);

ipc_client_close_connection(connection);

Expand Down Expand Up @@ -458,7 +487,7 @@ static int do_io(struct ipc_server_thread_data *server_thread_data)
if (ret >= 0) {
ret = server_thread_data->server_data->application_cb(
server_thread_data->server_data->application_data,
buf.buf, do_io_reply_callback, &reply_data);
buf.buf, buf.len, do_io_reply_callback, &reply_data);

packet_flush_gently(reply_data.fd);

Expand Down Expand Up @@ -565,11 +594,132 @@ static void *server_thread_proc(void *_server_thread_data)
return NULL;
}

/*
* We need to build a Windows "SECURITY_ATTRIBUTES" object and use it
* to apply an ACL when we create the initial instance of the Named
* Pipe. The construction is somewhat involved and consists of
* several sequential steps and intermediate objects.
*
* We use this structure to hold these intermediate pointers so that
* we can free them as a group. (It is unclear from the docs whether
* some of these intermediate pointers can be freed before we are
* finished using the "lpSA" member.)
*/
struct my_sa_data
{
PSID pEveryoneSID;
PACL pACL;
PSECURITY_DESCRIPTOR pSD;
LPSECURITY_ATTRIBUTES lpSA;
};

static void init_sa(struct my_sa_data *d)
{
memset(d, 0, sizeof(*d));
}

static void release_sa(struct my_sa_data *d)
{
if (d->pEveryoneSID)
FreeSid(d->pEveryoneSID);
if (d->pACL)
LocalFree(d->pACL);
if (d->pSD)
LocalFree(d->pSD);
if (d->lpSA)
LocalFree(d->lpSA);

memset(d, 0, sizeof(*d));
}

/*
* Create SECURITY_ATTRIBUTES to apply to the initial named pipe. The
* creator of the first server instance gets to set the ACLs on it.
*
* We allow the well-known group `EVERYONE` to have read+write access
* to the named pipe so that clients can send queries to the daemon
* and receive the response.
*
* Normally, this is not necessary since the daemon is usually
* automatically started by a foreground command like `git status`,
* but in those cases where an elevated Git command started the daemon
* (such that the daemon itself runs with elevation), we need to add
* the ACL so that non-elevated commands can write to it.
*
* The following document was helpful:
* https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
*
* Returns d->lpSA set to a SA or NULL.
*/
static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d)
{
SID_IDENTIFIER_AUTHORITY sid_auth_world = SECURITY_WORLD_SID_AUTHORITY;
#define NR_EA (1)
EXPLICIT_ACCESS ea[NR_EA];
DWORD dwResult;

if (!AllocateAndInitializeSid(&sid_auth_world, 1,
SECURITY_WORLD_RID, 0,0,0,0,0,0,0,
&d->pEveryoneSID)) {
DWORD gle = GetLastError();
trace2_data_intmax("ipc-debug", NULL, "alloc-world-sid/gle",
(intmax_t)gle);
goto fail;
}

memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS));

ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
ea[0].grfAccessMode = SET_ACCESS;
ea[0].grfInheritance = NO_INHERITANCE;
ea[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.ptstrName = (LPTSTR)d->pEveryoneSID;

dwResult = SetEntriesInAcl(NR_EA, ea, NULL, &d->pACL);
if (dwResult != ERROR_SUCCESS) {
DWORD gle = GetLastError();
trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/gle",
(intmax_t)gle);
trace2_data_intmax("ipc-debug", NULL, "set-acl-entry/dw",
(intmax_t)dwResult);
goto fail;
}

d->pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(
LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
if (!InitializeSecurityDescriptor(d->pSD, SECURITY_DESCRIPTOR_REVISION)) {
DWORD gle = GetLastError();
trace2_data_intmax("ipc-debug", NULL, "init-sd/gle", (intmax_t)gle);
goto fail;
}

if (!SetSecurityDescriptorDacl(d->pSD, TRUE, d->pACL, FALSE)) {
DWORD gle = GetLastError();
trace2_data_intmax("ipc-debug", NULL, "set-sd-dacl/gle", (intmax_t)gle);
goto fail;
}

d->lpSA = (LPSECURITY_ATTRIBUTES)LocalAlloc(LPTR, sizeof(SECURITY_ATTRIBUTES));
d->lpSA->nLength = sizeof(SECURITY_ATTRIBUTES);
d->lpSA->lpSecurityDescriptor = d->pSD;
d->lpSA->bInheritHandle = FALSE;

return d->lpSA;

fail:
release_sa(d);
return NULL;
}

static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
{
HANDLE hPipe;
DWORD dwOpenMode, dwPipeMode;
LPSECURITY_ATTRIBUTES lpsa = NULL;
struct my_sa_data my_sa_data;

init_sa(&my_sa_data);

dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND |
FILE_FLAG_OVERLAPPED;
Expand All @@ -585,20 +735,15 @@ static HANDLE create_new_pipe(wchar_t *wpath, int is_first)
* set the ACL / Security Attributes on the named
* pipe; subsequent instances inherit and cannot
* change them.
*
* TODO Should we allow the application layer to
* specify security attributes, such as `LocalService`
* or `LocalSystem`, when we create the named pipe?
* This question is probably not important when the
* daemon is started by a foreground user process and
* only needs to talk to the current user, but may be
* if the daemon is run via the Control Panel as a
* System Service.
*/
get_sa(&my_sa_data);
}

hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode,
PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa);
PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0,
my_sa_data.lpSA);

release_sa(&my_sa_data);

return hPipe;
}
Expand Down