Skip to content

Commit b1d9837

Browse files
Unique-Usmangitster
authored andcommitted
connect: advertise OS version
As some issues that can happen with a Git client can be operating system specific, it can be useful for a server to know which OS a client is using. In the same way it can be useful for a client to know which OS a server is using. Let's introduce a new protocol (`os-version`) allowing Git clients and servers to exchange operating system information. Having the `os-version` protocol capability separately from other protocol capabilities like `agent` is beneficial in ways like: - It provides a clear separation between Git versioning and OS-specific, concerns making troubleshooting and environment analysis more modular. - It ensures we do not disrupt people's scripts that collect statistics from other protocol capabilities like `agent`. - It offers flexibility for possible future extensibility, allowing us to add additional system-level details without modifying existing `agent` parsing logic. - It provides better control over privacy and security by allowing selective exposure of OS information. Add the `transfer.advertiseOSVersion` config option to address privacy concerns. It defaults to `true` and can be changed to `false`. When enabled, this option makes clients and servers send each other the OS name (e.g., "Linux" or "Windows"). The information is retrieved using the 'sysname' field of the `uname(2)` system call or its equivalent. However, there are differences between `uname(1)` (command-line utility) and `uname(2)` (system call) outputs on Windows. These discrepancies complicate testing on Windows platforms. For example: - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\ .2024-02-14.20:17.UTC.x86_64 - `uname(2)` output: Windows.10.0.20348 On Windows, uname(2) is not actually system-supplied but is instead already faked up by Git itself. We could have overcome the test issue on Windows by implementing a new `uname` subcommand in `test-tool` using uname(2), but except uname(2), which would be tested against itself, there would be nothing platform specific, so it's just simpler to disable the tests on Windows. Mentored-by: Christian Couder <[email protected]> Signed-off-by: Usman Akinyemi <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8f8d4cd commit b1d9837

File tree

9 files changed

+121
-4
lines changed

9 files changed

+121
-4
lines changed

Documentation/config/transfer.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,10 @@ transfer.bundleURI::
125125
transfer.advertiseObjectInfo::
126126
When `true`, the `object-info` capability is advertised by
127127
servers. Defaults to false.
128+
129+
transfer.advertiseOSVersion::
130+
When set to `true` on the server, the server will advertise its
131+
`os-version` capability to the client. On the client side, if set
132+
to `true`, it will advertise its `os-version` capability to the
133+
server only if the server also advertises its `os-version` capability.
134+
Defaults to true.

Documentation/gitprotocol-v2.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ printable ASCII characters except space (i.e., the byte range 32 < x <
190190
and debugging purposes, and MUST NOT be used to programmatically assume
191191
the presence or absence of particular features.
192192

193+
os-version
194+
~~~~~~~~~~
195+
196+
In the same way as the `agent` capability above, the server can
197+
advertise the `os-version` capability to notify the client the
198+
kind of operating system it is running on. The client may optionally
199+
send its own `os-version` capability, to notify the server the kind
200+
of operating system it is also running on in its request to the server
201+
(but it MUST NOT do so if the server did not advertise the os-version
202+
capability). The value of this capability may consist of ASCII printable
203+
characters(from 33 to 126 inclusive) and are typically made from the
204+
result of `uname -s`(OS name e.g Linux). The os-version capability can
205+
be disabled entirely by setting the `transfer.advertiseOSVersion` config
206+
option to `false`. The `os-version` strings are purely informative for
207+
statistics and debugging purposes, and MUST NOT be used to
208+
programmatically assume the presence or absence of particular features.
209+
193210
ls-refs
194211
~~~~~~~
195212

connect.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)
492492
if (server_supports_v2("agent"))
493493
packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
494494

495+
if (server_supports_v2("os-version") && advertise_os_version(the_repository))
496+
packet_write_fmt(fd_out, "os-version=%s", os_version_sanitized());
497+
495498
if (server_feature_v2("object-format", &hash_name)) {
496499
int hash_algo = hash_algo_by_name(hash_name);
497500
if (hash_algo == GIT_HASH_UNKNOWN)

serve.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ static int agent_advertise(struct repository *r UNUSED,
3131
return 1;
3232
}
3333

34+
static int os_version_advertise(struct repository *r,
35+
struct strbuf *value)
36+
{
37+
if (!advertise_os_version(r))
38+
return 0;
39+
if (value)
40+
strbuf_addstr(value, os_version_sanitized());
41+
return 1;
42+
}
43+
3444
static int object_format_advertise(struct repository *r,
3545
struct strbuf *value)
3646
{
@@ -123,6 +133,10 @@ static struct protocol_capability capabilities[] = {
123133
.name = "agent",
124134
.advertise = agent_advertise,
125135
},
136+
{
137+
.name = "os-version",
138+
.advertise = os_version_advertise,
139+
},
126140
{
127141
.name = "ls-refs",
128142
.advertise = ls_refs_advertise,

t/t5555-http-smart-common.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,17 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '
123123
'
124124

125125
test_expect_success 'git upload-pack --advertise-refs: v2' '
126+
printf "agent=FAKE\n" >agent_and_osversion &&
127+
if test_have_prereq WINDOWS
128+
then
129+
git config transfer.advertiseOSVersion false
130+
else
131+
printf "os-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_osversion
132+
fi &&
133+
126134
cat >expect <<-EOF &&
127135
version 2
128-
agent=FAKE
136+
$(cat agent_and_osversion)
129137
ls-refs=unborn
130138
fetch=shallow wait-for-done
131139
server-option

t/t5701-git-serve.sh

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,29 @@ test_expect_success 'setup to generate files with expected content' '
2323
server-option
2424
object-format=$(test_oid algo)
2525
EOF
26-
cat >expect.trailer <<-EOF
26+
cat >expect.trailer <<-EOF &&
2727
0000
2828
EOF
29+
30+
if test_have_prereq WINDOWS
31+
then
32+
git config transfer.advertiseOSVersion false
33+
else
34+
printf "os-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_osversion
35+
fi &&
36+
37+
cat >expect_osversion.base <<-EOF
38+
version 2
39+
$(cat agent_and_osversion)
40+
ls-refs=unborn
41+
fetch=shallow wait-for-done
42+
server-option
43+
object-format=$(test_oid algo)
44+
EOF
2945
'
3046

3147
test_expect_success 'test capability advertisement' '
32-
cat expect.base expect.trailer >expect &&
48+
cat expect_osversion.base expect.trailer >expect &&
3349
3450
GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \
3551
--advertise-capabilities >out &&
@@ -357,7 +373,7 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund
357373
cat >expect.extra <<-EOF &&
358374
bundle-uri
359375
EOF
360-
cat expect.base \
376+
cat expect_osversion.base \
361377
expect.extra \
362378
expect.trailer >expect &&
363379

t/test-lib-functions.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2007,3 +2007,11 @@ test_trailing_hash () {
20072007
test-tool hexdump |
20082008
sed "s/ //g"
20092009
}
2010+
2011+
# Trim and replace each character with ascii code below 32 or above
2012+
# 127 (included) using a dot '.' character.
2013+
# Octal intervals \001-\040 and \177-\377
2014+
# correspond to decimal intervals 1-32 and 127-255
2015+
test_redact_non_printables () {
2016+
tr -d "\n\r" | tr "[\001-\040][\177-\377]" "."
2017+
}

version.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "strbuf.h"
55
#include "sane-ctype.h"
66
#include "gettext.h"
7+
#include "config.h"
78

89
const char git_version_string[] = GIT_VERSION;
910
const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;
@@ -69,3 +70,31 @@ int get_uname_info(struct strbuf *buf, unsigned int full)
6970
strbuf_addf(buf, "%s\n", uname_info.sysname);
7071
return 0;
7172
}
73+
74+
const char *os_version_sanitized(void)
75+
{
76+
static const char *os = NULL;
77+
78+
if (!os) {
79+
struct strbuf buf = STRBUF_INIT;
80+
81+
get_uname_info(&buf, 0);
82+
/* Sanitize the os information immediately */
83+
redact_non_printables(&buf);
84+
os = strbuf_detach(&buf, NULL);
85+
}
86+
87+
return os;
88+
}
89+
90+
int advertise_os_version(struct repository *r)
91+
{
92+
static int transfer_advertise_os_version = -1;
93+
94+
if (transfer_advertise_os_version == -1) {
95+
repo_config_get_bool(r, "transfer.advertiseosversion", &transfer_advertise_os_version);
96+
/* enabled by default */
97+
transfer_advertise_os_version = !!transfer_advertise_os_version;
98+
}
99+
return transfer_advertise_os_version;
100+
}

version.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef VERSION_H
22
#define VERSION_H
33

4+
struct repository;
5+
46
extern const char git_version_string[];
57
extern const char git_built_from_commit_string[];
68

@@ -14,4 +16,17 @@ const char *git_user_agent_sanitized(void);
1416
*/
1517
int get_uname_info(struct strbuf *buf, unsigned int full);
1618

19+
/*
20+
Retrieve, sanitize and cache system information for subsequent
21+
calls. Return a pointer to the sanitized system information
22+
string.
23+
*/
24+
const char *os_version_sanitized(void);
25+
26+
/*
27+
Retrieve and cache whether os-version capability is enabled.
28+
Return 1 if enabled, 0 if disabled.
29+
*/
30+
int advertise_os_version(struct repository *r);
31+
1732
#endif /* VERSION_H */

0 commit comments

Comments
 (0)