Skip to content

Commit d52356d

Browse files
committed
Merge branch 'ua/os-version-capability' into seen
The value of "uname -s" is by default sent over the wire as a new capability, with an opt-out for privacy-concious folks. * ua/os-version-capability: version: introduce osversion.command config for os-version output connect: advertise OS version version: refactor get_uname_info() version: refactor redact_non_printables()
2 parents 422115d + cbb94dc commit d52356d

File tree

10 files changed

+291
-19
lines changed

10 files changed

+291
-19
lines changed

Documentation/config/transfer.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,19 @@ 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 `true`, the `os-version` capability is advertised by clients and
131+
servers. It makes clients and servers send to each other a string
132+
representing the operating system name, like "Linux" or "Windows".
133+
This string is retrieved from the 'sysname' field of the struct returned
134+
by the uname(2) system call. If the `osVersion.command` is set, the
135+
output of the command specified will be the string exchanged by the clients
136+
and the servers. Defaults to true.
137+
138+
osVersion.command::
139+
If this variable is set, the specified command will be run and the output
140+
will be used as the value `X` for `os-version` capability (in the form
141+
`os-version=X`). `osVersion.command` is only used if `transfer.advertiseOSVersion`
142+
is true. Refer to the linkgit:git-config[1] documentation to learn more about
143+
`transfer.advertiseOSVersion` config option.

Documentation/gitprotocol-v2.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,27 @@ 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 with a value `X` (in the form
198+
`os-version=X`) to notify the client that the server is running an
199+
operating system that can be identified by `X`. The client may
200+
optionally send its own `os-version` string by including the
201+
`os-version` capability with a value `Y` (in the form `os-version=Y`)
202+
in its request to the server (but it MUST NOT do so if the server did
203+
not advertise the os-version capability). The `X` and `Y` strings may
204+
contain any printable ASCII characters except space (i.e., the byte
205+
range 32 < x < 127), and are typically made from the result of
206+
`uname -s`(OS name e.g Linux). If the `osVersion.command` is set,
207+
the `X` and `Y` are made from the ouput of the command specified.
208+
The os-version capability can be disabled entirely by setting the
209+
`transfer.advertiseOSVersion` config option to `false`. The `os-version`
210+
strings are purely informative for statistics and debugging purposes, and
211+
MUST NOT be used to programmatically assume the presence or absence of
212+
particular features.
213+
193214
ls-refs
194215
~~~~~~~
195216

builtin/bugreport.c

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
#include "diagnose.h"
1313
#include "object-file.h"
1414
#include "setup.h"
15+
#include "version.h"
1516

1617
static void get_system_info(struct strbuf *sys_info)
1718
{
18-
struct utsname uname_info;
1919
char *shell = NULL;
2020

2121
/* get git version from native cmd */
@@ -24,16 +24,7 @@ static void get_system_info(struct strbuf *sys_info)
2424

2525
/* system call for other version info */
2626
strbuf_addstr(sys_info, "uname: ");
27-
if (uname(&uname_info))
28-
strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
29-
strerror(errno),
30-
errno);
31-
else
32-
strbuf_addf(sys_info, "%s %s %s %s\n",
33-
uname_info.sysname,
34-
uname_info.release,
35-
uname_info.version,
36-
uname_info.machine);
27+
get_uname_info(sys_info, 1);
3728

3829
strbuf_addstr(sys_info, _("compiler info: "));
3930
get_compiler_info(sys_info);

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
@@ -29,6 +29,16 @@ static int agent_advertise(struct repository *r UNUSED,
2929
return 1;
3030
}
3131

32+
static int os_version_advertise(struct repository *r,
33+
struct strbuf *value)
34+
{
35+
if (!advertise_os_version(r))
36+
return 0;
37+
if (value)
38+
strbuf_addstr(value, os_version_sanitized());
39+
return 1;
40+
}
41+
3242
static int object_format_advertise(struct repository *r,
3343
struct strbuf *value)
3444
{
@@ -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: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,48 @@ 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" >agent_and_os_name &&
127+
if test_have_prereq WINDOWS
128+
then
129+
# We do not use test_config here so that any tests below can reuse
130+
# the "expect" file from this test
131+
git config transfer.advertiseOSVersion false
132+
else
133+
printf "\nos-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_os_name
134+
fi &&
135+
136+
cat >expect <<-EOF &&
137+
version 2
138+
$(cat agent_and_os_name)
139+
ls-refs=unborn
140+
fetch=shallow wait-for-done
141+
server-option
142+
object-format=$(test_oid algo)
143+
0000
144+
EOF
145+
146+
GIT_PROTOCOL=version=2 \
147+
GIT_USER_AGENT=FAKE \
148+
git upload-pack --advertise-refs . >out 2>err &&
149+
150+
test-tool pkt-line unpack <out >actual &&
151+
test_must_be_empty err &&
152+
test_cmp actual expect
153+
'
154+
155+
test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '
156+
# test_config is used here as we are not reusing any file output from here
157+
test_config osVersion.command "uname -srvm" &&
158+
printf "agent=FAKE" >agent_and_long_os_name &&
159+
160+
if test_have_prereq !WINDOWS
161+
then
162+
printf "\nos-version=%s\n" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name
163+
fi &&
164+
126165
cat >expect <<-EOF &&
127166
version 2
128-
agent=FAKE
167+
$(cat agent_and_long_os_name)
129168
ls-refs=unborn
130169
fetch=shallow wait-for-done
131170
server-option

t/t5701-git-serve.sh

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
88
. ./test-lib.sh
99

1010
test_expect_success 'test capability advertisement' '
11+
printf "agent=git/$(git version | cut -d" " -f3)" >agent_and_os_name &&
12+
if test_have_prereq WINDOWS
13+
then
14+
# We do not use test_config here so that tests below will be able to reuse
15+
# the expect.base and expect.trailer files
16+
git config transfer.advertiseOSVersion false
17+
else
18+
printf "\nos-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_os_name
19+
fi &&
20+
1121
test_oid_cache <<-EOF &&
1222
wrong_algo sha1:sha256
1323
wrong_algo sha256:sha1
1424
EOF
1525
cat >expect.base <<-EOF &&
1626
version 2
17-
agent=git/$(git version | cut -d" " -f3)
27+
$(cat agent_and_os_name)
1828
ls-refs=unborn
1929
fetch=shallow wait-for-done
2030
server-option
@@ -31,6 +41,39 @@ test_expect_success 'test capability advertisement' '
3141
test_cmp expect actual
3242
'
3343

44+
test_expect_success 'test capability advertisement with osVersion.command config set' '
45+
# test_config is used here as we are not reusing any file output from here
46+
test_config osVersion.command "uname -srvm" &&
47+
printf "agent=git/$(git version | cut -d" " -f3)" >agent_and_long_os_name &&
48+
49+
if test_have_prereq !WINDOWS
50+
then
51+
printf "\nos-version=%s\n" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name
52+
fi &&
53+
54+
test_oid_cache <<-EOF &&
55+
wrong_algo sha1:sha256
56+
wrong_algo sha256:sha1
57+
EOF
58+
cat >expect.base_long <<-EOF &&
59+
version 2
60+
$(cat agent_and_long_os_name)
61+
ls-refs=unborn
62+
fetch=shallow wait-for-done
63+
server-option
64+
object-format=$(test_oid algo)
65+
EOF
66+
cat >expect.trailer_long <<-EOF &&
67+
0000
68+
EOF
69+
cat expect.base_long expect.trailer_long >expect &&
70+
71+
GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \
72+
--advertise-capabilities >out &&
73+
test-tool pkt-line unpack <out >actual &&
74+
test_cmp expect actual
75+
'
76+
3477
test_expect_success 'stateless-rpc flag does not list capabilities' '
3578
# Empty request
3679
test-tool pkt-line pack >in <<-EOF &&

t/test-lib-functions.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2043,3 +2043,11 @@ test_trailing_hash () {
20432043
test-tool hexdump |
20442044
sed "s/ //g"
20452045
}
2046+
2047+
# Trim and replace each character with ascii code below 32 or above
2048+
# 127 (included) using a dot '.' character.
2049+
# Octal intervals \001-\040 and \177-\377
2050+
# corresponds to decimal intervals 1-32 and 127-255
2051+
test_redact_non_printables () {
2052+
tr -d "\n" | tr "[\001-\040][\177-\377]" "."
2053+
}

version.c

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,31 @@
1+
#define USE_THE_REPOSITORY_VARIABLE
2+
13
#include "git-compat-util.h"
24
#include "version.h"
35
#include "version-def.h"
46
#include "strbuf.h"
7+
#include "gettext.h"
8+
#include "config.h"
9+
#include "run-command.h"
10+
#include "alias.h"
511

612
const char git_version_string[] = GIT_VERSION;
713
const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;
814

15+
/*
16+
* Trim and replace each character with ascii code below 32 or above
17+
* 127 (included) using a dot '.' character.
18+
* TODO: ensure consecutive non-printable characters are only replaced once
19+
*/
20+
static void redact_non_printables(struct strbuf *buf)
21+
{
22+
strbuf_trim(buf);
23+
for (size_t i = 0; i < buf->len; i++) {
24+
if (buf->buf[i] <= 32 || buf->buf[i] >= 127)
25+
buf->buf[i] = '.';
26+
}
27+
}
28+
929
const char *git_user_agent(void)
1030
{
1131
static const char *agent = NULL;
@@ -27,13 +47,117 @@ const char *git_user_agent_sanitized(void)
2747
struct strbuf buf = STRBUF_INIT;
2848

2949
strbuf_addstr(&buf, git_user_agent());
30-
strbuf_trim(&buf);
31-
for (size_t i = 0; i < buf.len; i++) {
32-
if (buf.buf[i] <= 32 || buf.buf[i] >= 127)
33-
buf.buf[i] = '.';
34-
}
35-
agent = buf.buf;
50+
redact_non_printables(&buf);
51+
agent = strbuf_detach(&buf, NULL);
3652
}
3753

3854
return agent;
3955
}
56+
57+
int get_uname_info(struct strbuf *buf, unsigned int full)
58+
{
59+
struct utsname uname_info;
60+
61+
if (uname(&uname_info)) {
62+
strbuf_addf(buf, _("uname() failed with error '%s' (%d)\n"),
63+
strerror(errno),
64+
errno);
65+
return -1;
66+
}
67+
68+
if (full)
69+
strbuf_addf(buf, "%s %s %s %s\n",
70+
uname_info.sysname,
71+
uname_info.release,
72+
uname_info.version,
73+
uname_info.machine);
74+
else
75+
strbuf_addf(buf, "%s\n", uname_info.sysname);
76+
return 0;
77+
}
78+
79+
/*
80+
* Return -1 if unable to retrieve the osversion.command config or
81+
* if the command is malformed; otherwise, return 0 if successful.
82+
*/
83+
static int fill_os_version_command(struct child_process *cmd)
84+
{
85+
const char *os_version_command;
86+
const char **argv;
87+
char *os_version_copy;
88+
int n;
89+
90+
if (git_config_get_string_tmp("osversion.command", &os_version_command))
91+
return -1;
92+
93+
os_version_copy = xstrdup(os_version_command);
94+
n = split_cmdline(os_version_copy, &argv);
95+
96+
if (n < 0) {
97+
warning(_("malformed osVersion.command config option: %s"),
98+
_(split_cmdline_strerror(n)));
99+
free(os_version_copy);
100+
return -1;
101+
}
102+
103+
for (int i = 0; i < n; i++)
104+
strvec_push(&cmd->args, argv[i]);
105+
free(os_version_copy);
106+
free(argv);
107+
108+
return 0;
109+
}
110+
111+
static int capture_os_version(struct strbuf *buf)
112+
{
113+
struct child_process cmd = CHILD_PROCESS_INIT;
114+
115+
if (fill_os_version_command(&cmd))
116+
return -1;
117+
if (capture_command(&cmd, buf, 0))
118+
return -1;
119+
120+
return 0;
121+
}
122+
123+
const char *os_version(void)
124+
{
125+
static const char *os = NULL;
126+
127+
if (!os) {
128+
struct strbuf buf = STRBUF_INIT;
129+
130+
if (capture_os_version(&buf))
131+
get_uname_info(&buf, 0);
132+
os = strbuf_detach(&buf, NULL);
133+
}
134+
135+
return os;
136+
}
137+
138+
const char *os_version_sanitized(void)
139+
{
140+
static const char *os_sanitized = NULL;
141+
142+
if (!os_sanitized) {
143+
struct strbuf buf = STRBUF_INIT;
144+
145+
strbuf_addstr(&buf, os_version());
146+
redact_non_printables(&buf);
147+
os_sanitized = strbuf_detach(&buf, NULL);
148+
}
149+
150+
return os_sanitized;
151+
}
152+
153+
int advertise_os_version(struct repository *r)
154+
{
155+
static int transfer_advertise_os_version = -1;
156+
157+
if (transfer_advertise_os_version == -1) {
158+
repo_config_get_bool(r, "transfer.advertiseosversion", &transfer_advertise_os_version);
159+
/* enabled by default */
160+
transfer_advertise_os_version = !!transfer_advertise_os_version;
161+
}
162+
return transfer_advertise_os_version;
163+
}

0 commit comments

Comments
 (0)