Skip to content

Commit

Permalink
Merge branch 'ua/os-version-capability' into seen
Browse files Browse the repository at this point in the history
* 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()
  • Loading branch information
gitster committed Jan 6, 2025
2 parents 4f34505 + cbb94dc commit 68f30f7
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 19 deletions.
16 changes: 16 additions & 0 deletions Documentation/config/transfer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,19 @@ 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. If the `osVersion.command` is set, the
output of the command specified will be the string exchanged by the clients
and the servers. Defaults to true.

osVersion.command::
If this variable is set, the specified command will be run and the output
will be used as the value `X` for `os-version` capability (in the form
`os-version=X`). `osVersion.command` is only used if `transfer.advertiseOSVersion`
is true. Refer to the linkgit:git-config[1] documentation to learn more about
`transfer.advertiseOSVersion` config option.
21 changes: 21 additions & 0 deletions Documentation/gitprotocol-v2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ 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 with a value `X` (in the form
`os-version=X`) to notify the client that the server is running an
operating system that can be identified by `X`. The client may
optionally send its own `os-version` string by including the
`os-version` capability with a value `Y` (in the form `os-version=Y`)
in its request to the server (but it MUST NOT do so if the server did
not advertise the os-version capability). The `X` and `Y` strings may
contain any printable ASCII characters except space (i.e., the byte
range 32 < x < 127), and are typically made from the result of
`uname -s`(OS name e.g Linux). If the `osVersion.command` is set,
the `X` and `Y` are made from the ouput of the command specified.
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
13 changes: 2 additions & 11 deletions builtin/bugreport.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
#include "diagnose.h"
#include "object-file.h"
#include "setup.h"
#include "version.h"

static void get_system_info(struct strbuf *sys_info)
{
struct utsname uname_info;
char *shell = NULL;

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

/* system call for other version info */
strbuf_addstr(sys_info, "uname: ");
if (uname(&uname_info))
strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"),
strerror(errno),
errno);
else
strbuf_addf(sys_info, "%s %s %s %s\n",
uname_info.sysname,
uname_info.release,
uname_info.version,
uname_info.machine);
get_uname_info(sys_info, 1);

strbuf_addstr(sys_info, _("compiler info: "));
get_compiler_info(sys_info);
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 @@ -29,6 +29,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
41 changes: 40 additions & 1 deletion t/t5555-http-smart-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,48 @@ test_expect_success 'git receive-pack --advertise-refs: v1' '
'

test_expect_success 'git upload-pack --advertise-refs: v2' '
printf "agent=FAKE" >agent_and_os_name &&
if test_have_prereq WINDOWS
then
# We do not use test_config here so that any tests below can reuse
# the "expect" file from this test
git config transfer.advertiseOSVersion false
else
printf "\nos-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_os_name
fi &&
cat >expect <<-EOF &&
version 2
$(cat agent_and_os_name)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
object-format=$(test_oid algo)
0000
EOF
GIT_PROTOCOL=version=2 \
GIT_USER_AGENT=FAKE \
git upload-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect
'

test_expect_success 'git upload-pack --advertise-refs: v2 with osVersion.command config set' '
# test_config is used here as we are not reusing any file output from here
test_config osVersion.command "uname -srvm" &&
printf "agent=FAKE" >agent_and_long_os_name &&
if test_have_prereq !WINDOWS
then
printf "\nos-version=%s\n" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name
fi &&
cat >expect <<-EOF &&
version 2
agent=FAKE
$(cat agent_and_long_os_name)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
Expand Down
45 changes: 44 additions & 1 deletion t/t5701-git-serve.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh

test_expect_success 'test capability advertisement' '
printf "agent=git/$(git version | cut -d" " -f3)" >agent_and_os_name &&
if test_have_prereq WINDOWS
then
# We do not use test_config here so that tests below will be able to reuse
# the expect.base and expect.trailer files
git config transfer.advertiseOSVersion false
else
printf "\nos-version=%s\n" $(uname -s | test_redact_non_printables) >>agent_and_os_name
fi &&
test_oid_cache <<-EOF &&
wrong_algo sha1:sha256
wrong_algo sha256:sha1
EOF
cat >expect.base <<-EOF &&
version 2
agent=git/$(git version | cut -d" " -f3)
$(cat agent_and_os_name)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
Expand All @@ -31,6 +41,39 @@ test_expect_success 'test capability advertisement' '
test_cmp expect actual
'

test_expect_success 'test capability advertisement with osVersion.command config set' '
# test_config is used here as we are not reusing any file output from here
test_config osVersion.command "uname -srvm" &&
printf "agent=git/$(git version | cut -d" " -f3)" >agent_and_long_os_name &&
if test_have_prereq !WINDOWS
then
printf "\nos-version=%s\n" $(uname -srvm | test_redact_non_printables) >>agent_and_long_os_name
fi &&
test_oid_cache <<-EOF &&
wrong_algo sha1:sha256
wrong_algo sha256:sha1
EOF
cat >expect.base_long <<-EOF &&
version 2
$(cat agent_and_long_os_name)
ls-refs=unborn
fetch=shallow wait-for-done
server-option
object-format=$(test_oid algo)
EOF
cat >expect.trailer_long <<-EOF &&
0000
EOF
cat expect.base_long expect.trailer_long >expect &&
GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \
--advertise-capabilities >out &&
test-tool pkt-line unpack <out >actual &&
test_cmp expect actual
'

test_expect_success 'stateless-rpc flag does not list capabilities' '
# Empty request
test-tool pkt-line pack >in <<-EOF &&
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 @@ -2043,3 +2043,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" | tr "[\001-\040][\177-\377]" "."
}
136 changes: 130 additions & 6 deletions version.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
#define USE_THE_REPOSITORY_VARIABLE

#include "git-compat-util.h"
#include "version.h"
#include "version-def.h"
#include "strbuf.h"
#include "gettext.h"
#include "config.h"
#include "run-command.h"
#include "alias.h"

const char git_version_string[] = GIT_VERSION;
const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT;

/*
* Trim and replace each character with ascii code below 32 or above
* 127 (included) using a dot '.' character.
* TODO: ensure consecutive non-printable characters are only replaced once
*/
static void redact_non_printables(struct strbuf *buf)
{
strbuf_trim(buf);
for (size_t i = 0; i < buf->len; i++) {
if (buf->buf[i] <= 32 || buf->buf[i] >= 127)
buf->buf[i] = '.';
}
}

const char *git_user_agent(void)
{
static const char *agent = NULL;
Expand All @@ -27,13 +47,117 @@ const char *git_user_agent_sanitized(void)
struct strbuf buf = STRBUF_INIT;

strbuf_addstr(&buf, git_user_agent());
strbuf_trim(&buf);
for (size_t i = 0; i < buf.len; i++) {
if (buf.buf[i] <= 32 || buf.buf[i] >= 127)
buf.buf[i] = '.';
}
agent = buf.buf;
redact_non_printables(&buf);
agent = strbuf_detach(&buf, NULL);
}

return agent;
}

int get_uname_info(struct strbuf *buf, unsigned int full)
{
struct utsname uname_info;

if (uname(&uname_info)) {
strbuf_addf(buf, _("uname() failed with error '%s' (%d)\n"),
strerror(errno),
errno);
return -1;
}

if (full)
strbuf_addf(buf, "%s %s %s %s\n",
uname_info.sysname,
uname_info.release,
uname_info.version,
uname_info.machine);
else
strbuf_addf(buf, "%s\n", uname_info.sysname);
return 0;
}

/*
* Return -1 if unable to retrieve the osversion.command config or
* if the command is malformed; otherwise, return 0 if successful.
*/
static int fill_os_version_command(struct child_process *cmd)
{
const char *os_version_command;
const char **argv;
char *os_version_copy;
int n;

if (git_config_get_string_tmp("osversion.command", &os_version_command))
return -1;

os_version_copy = xstrdup(os_version_command);
n = split_cmdline(os_version_copy, &argv);

if (n < 0) {
warning(_("malformed osVersion.command config option: %s"),
_(split_cmdline_strerror(n)));
free(os_version_copy);
return -1;
}

for (int i = 0; i < n; i++)
strvec_push(&cmd->args, argv[i]);
free(os_version_copy);
free(argv);

return 0;
}

static int capture_os_version(struct strbuf *buf)
{
struct child_process cmd = CHILD_PROCESS_INIT;

if (fill_os_version_command(&cmd))
return -1;
if (capture_command(&cmd, buf, 0))
return -1;

return 0;
}

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

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

if (capture_os_version(&buf))
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;
}
Loading

0 comments on commit 68f30f7

Please sign in to comment.