Skip to content

Commit

Permalink
connect: advertise OS version
Browse files Browse the repository at this point in the history
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. The protocol is
controlled by the new `transfer.advertiseOSVersion` config option.

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.

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 <chriscool@tuxfamily.org>
Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Unique-Usman authored and gitster committed Jan 17, 2025
1 parent d4d6670 commit a0fc9a3
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 3 deletions.
7 changes: 7 additions & 0 deletions Documentation/config/transfer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,10 @@ transfer.bundleURI::
transfer.advertiseObjectInfo::
When `true`, the `object-info` capability is advertised by
servers. Defaults to false.

transfer.advertiseOSVersion::
When `true`, the `os-version` capability is advertised by clients and
servers. It makes clients and servers send to each other a string
representing the operating system name, like "Linux" or "Windows".
This string is retrieved from the `sysname` field of the struct returned
by the uname(2) system call. Defaults to true.
18 changes: 18 additions & 0 deletions Documentation/gitprotocol-v2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,24 @@ printable ASCII characters except space (i.e., the byte range 32 < x <
and debugging purposes, and MUST NOT be used to programmatically assume
the presence or absence of particular features.

os-version
~~~~~~~~~~

In the same way as the `agent` capability above, the server can
advertise the `os-version` capability to notify the client the
kind of operating system it is running on. The client may optionally
send its own `os-version` capability, to notify the server the kind of
operating system it is also running on in its request to the server
(but it MUST NOT do so if the server did not advertise the os-version
capability). The value of this capability may consist of ASCII printable
characters(from 33 to 126 inclusive) and are typically made from the result of
`uname -s`(OS name e.g Linux). The os-version capability can be disabled
entirely by setting the `transfer.advertiseOSVersion` config option
to `false`. The `os-version` strings are purely informative for
statistics and debugging purposes, and MUST NOT be used to
programmatically assume the presence or absence of particular
features.

ls-refs
~~~~~~~

Expand Down
3 changes: 3 additions & 0 deletions connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,9 @@ static void send_capabilities(int fd_out, struct packet_reader *reader)
if (server_supports_v2("agent"))
packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());

if (server_supports_v2("os-version") && advertise_os_version(the_repository))
packet_write_fmt(fd_out, "os-version=%s", os_version_sanitized());

if (server_feature_v2("object-format", &hash_name)) {
int hash_algo = hash_algo_by_name(hash_name);
if (hash_algo == GIT_HASH_UNKNOWN)
Expand Down
14 changes: 14 additions & 0 deletions serve.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ static int agent_advertise(struct repository *r UNUSED,
return 1;
}

static int os_version_advertise(struct repository *r,
struct strbuf *value)
{
if (!advertise_os_version(r))
return 0;
if (value)
strbuf_addstr(value, os_version_sanitized());
return 1;
}

static int object_format_advertise(struct repository *r,
struct strbuf *value)
{
Expand Down Expand Up @@ -123,6 +133,10 @@ static struct protocol_capability capabilities[] = {
.name = "agent",
.advertise = agent_advertise,
},
{
.name = "os-version",
.advertise = os_version_advertise,
},
{
.name = "ls-refs",
.advertise = ls_refs_advertise,
Expand Down
10 changes: 9 additions & 1 deletion t/t5555-http-smart-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,17 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '
'

test_expect_success 'git upload-pack --advertise-refs: v2' '
printf "agent=FAKE" >agent_and_osversion &&
if test_have_prereq WINDOWS
then
git config transfer.advertiseOSVersion false
else
printf "\nos-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_osversion
fi &&
cat >expect <<-EOF &&
version 2
agent=FAKE
$(cat agent_and_osversion)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
Expand Down
20 changes: 18 additions & 2 deletions t/t5701-git-serve.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,26 @@ test_expect_success 'setup to generate files with expected content' '
cat >expect.trailer <<-EOF &&
0000
EOF
if test_have_prereq WINDOWS
then
git config transfer.advertiseOSVersion false
else
printf "\nos-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_osversion
fi &&
cat >expect_osversion.base <<-EOF
version 2
$(cat agent_and_osversion)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
object-format=$(test_oid algo)
EOF
'

test_expect_success 'test capability advertisement' '
cat expect.base expect.trailer >expect &&
cat expect_osversion.base expect.trailer >expect &&
GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \
--advertise-capabilities >out &&
Expand Down Expand Up @@ -357,7 +373,7 @@ test_expect_success 'test capability advertisement with uploadpack.advertiseBund
cat >expect.extra <<-EOF &&
bundle-uri
EOF
cat expect.base \
cat expect_osversion.base \
expect.extra \
expect.trailer >expect &&
Expand Down
8 changes: 8 additions & 0 deletions t/test-lib-functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2007,3 +2007,11 @@ test_trailing_hash () {
test-tool hexdump |
sed "s/ //g"
}

# Trim and replace each character with ascii code below 32 or above
# 127 (included) using a dot '.' character.
# Octal intervals \001-\040 and \177-\377
# corresponds to decimal intervals 1-32 and 127-255
test_redact_non_printables () {
tr -d "\n\r" | tr "[\001-\040][\177-\377]" "."
}
42 changes: 42 additions & 0 deletions version.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "version-def.h"
#include "strbuf.h"
#include "gettext.h"
#include "config.h"

const char git_version_string[] = GIT_VERSION;
const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;
Expand Down Expand Up @@ -69,3 +70,44 @@ int get_uname_info(struct strbuf *buf, unsigned int full)
strbuf_addf(buf, "%s\n", uname_info.sysname);
return 0;
}

const char *os_version(void)
{
static const char *os = NULL;

if (!os) {
struct strbuf buf = STRBUF_INIT;

get_uname_info(&buf, 0);
os = strbuf_detach(&buf, NULL);
}

return os;
}

const char *os_version_sanitized(void)
{
static const char *os_sanitized = NULL;

if (!os_sanitized) {
struct strbuf buf = STRBUF_INIT;

strbuf_addstr(&buf, os_version());
redact_non_printables(&buf);
os_sanitized = strbuf_detach(&buf, NULL);
}

return os_sanitized;
}

int advertise_os_version(struct repository *r)
{
static int transfer_advertise_os_version = -1;

if (transfer_advertise_os_version == -1) {
repo_config_get_bool(r, "transfer.advertiseosversion", &transfer_advertise_os_version);
/* enabled by default */
transfer_advertise_os_version = !!transfer_advertise_os_version;
}
return transfer_advertise_os_version;
}
21 changes: 21 additions & 0 deletions version.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef VERSION_H
#define VERSION_H

struct repository;

extern const char git_version_string[];
extern const char git_built_from_commit_string[];

Expand All @@ -14,4 +16,23 @@ const char *git_user_agent_sanitized(void);
*/
int get_uname_info(struct strbuf *buf, unsigned int full);

/*
Retrieve and cache system information for subsequent calls.
Return a pointer to the cached system information string.
*/
const char *os_version(void);

/*
Retrieve system information string from os_version(). Then
sanitize and cache it. Return a pointer to the sanitized
system information string.
*/
const char *os_version_sanitized(void);

/*
Retrieve and cache whether os-version capability is enabled.
Return 1 if enabled, 0 if disabled.
*/
int advertise_os_version(struct repository *r);

#endif /* VERSION_H */

0 comments on commit a0fc9a3

Please sign in to comment.