1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
|
#define USE_THE_REPOSITORY_VARIABLE
#include "../../git-compat-util.h"
#include "../../environment.h"
#include "../../wrapper.h"
#include "../../strbuf.h"
#include "../../versioncmp.h"
int win32_has_dos_drive_prefix(const char *path)
{
int i;
/*
* Does it start with an ASCII letter (i.e. highest bit not set),
* followed by a colon?
*/
if (!(0x80 & (unsigned char)*path))
return *path && path[1] == ':' ? 2 : 0;
/*
* While drive letters must be letters of the English alphabet, it is
* possible to assign virtually _any_ Unicode character via `subst` as
* a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
* like this:
*
* subst ֍: %USERPROFILE%\Desktop
*/
for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
; /* skip first UTF-8 character */
return path[i] == ':' ? i + 1 : 0;
}
int win32_skip_dos_drive_prefix(char **path)
{
int ret = has_dos_drive_prefix(*path);
*path += ret;
return ret;
}
int win32_offset_1st_component(const char *path)
{
char *pos = (char *)path;
/* unc paths */
if (!skip_dos_drive_prefix(&pos) &&
is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
/* skip server name */
pos = strpbrk(pos + 2, "\\/");
if (!pos)
return 0; /* Error: malformed unc path */
do {
pos++;
} while (*pos && !is_dir_sep(*pos));
}
return pos + is_dir_sep(*pos) - path;
}
int win32_fspathncmp(const char *a, const char *b, size_t count)
{
int diff;
for (;;) {
if (!count--)
return 0;
if (!*a)
return *b ? -1 : 0;
if (!*b)
return +1;
if (is_dir_sep(*a)) {
if (!is_dir_sep(*b))
return -1;
a++;
b++;
continue;
} else if (is_dir_sep(*b))
return +1;
diff = ignore_case ?
(unsigned char)tolower(*a) - (int)(unsigned char)tolower(*b) :
(unsigned char)*a - (int)(unsigned char)*b;
if (diff)
return diff;
a++;
b++;
}
}
int win32_fspathcmp(const char *a, const char *b)
{
return win32_fspathncmp(a, b, (size_t)-1);
}
static int read_at(int fd, char *buffer, size_t offset, size_t size)
{
if (lseek(fd, offset, SEEK_SET) < 0) {
fprintf(stderr, "could not seek to 0x%x\n", (unsigned int)offset);
return -1;
}
return read_in_full(fd, buffer, size);
}
static size_t le16(const char *buffer)
{
unsigned char *u = (unsigned char *)buffer;
return u[0] | (u[1] << 8);
}
static size_t le32(const char *buffer)
{
return le16(buffer) | (le16(buffer + 2) << 16);
}
/*
* Determine the Go version of a given executable, if it was built with Go.
*
* This recapitulates the logic from
* https://github.com/golang/go/blob/master/src/cmd/go/internal/version/version.go
* (without requiring the user to install `go.exe` to find out).
*/
static ssize_t get_go_version(const char *path, char *go_version, size_t go_version_size)
{
int fd = open(path, O_RDONLY);
char buffer[1024];
off_t offset;
size_t num_sections, opt_header_size, i;
char *p = NULL, *q;
ssize_t res = -1;
if (fd < 0)
return -1;
if (read_in_full(fd, buffer, 2) < 0)
goto fail;
/*
* Parse the PE file format, for more details, see
* https://en.wikipedia.org/wiki/Portable_Executable#Layout and
* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
*/
if (buffer[0] != 'M' || buffer[1] != 'Z')
goto fail;
if (read_at(fd, buffer, 0x3c, 4) < 0)
goto fail;
/* Read the `PE\0\0` signature and the COFF file header */
offset = le32(buffer);
if (read_at(fd, buffer, offset, 24) < 0)
goto fail;
if (buffer[0] != 'P' || buffer[1] != 'E' || buffer[2] != '\0' || buffer[3] != '\0')
goto fail;
num_sections = le16(buffer + 6);
opt_header_size = le16(buffer + 20);
offset += 24; /* skip file header */
/*
* Validate magic number 0x10b or 0x20b, for full details see
* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only
*/
if (read_at(fd, buffer, offset, 2) < 0 ||
((i = le16(buffer)) != 0x10b && i != 0x20b))
goto fail;
offset += opt_header_size;
for (i = 0; i < num_sections; i++) {
if (read_at(fd, buffer, offset + i * 40, 40) < 0)
goto fail;
/*
* For full details about the section headers, see
* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers
*/
if ((le32(buffer + 36) /* characteristics */ & ~0x600000) /* IMAGE_SCN_ALIGN_32BYTES */ ==
(/* IMAGE_SCN_CNT_INITIALIZED_DATA */ 0x00000040 |
/* IMAGE_SCN_MEM_READ */ 0x40000000 |
/* IMAGE_SCN_MEM_WRITE */ 0x80000000)) {
size_t size = le32(buffer + 16); /* "SizeOfRawData " */
size_t pointer = le32(buffer + 20); /* "PointerToRawData " */
/*
* Skip the section if either size or pointer is 0, see
* https://github.com/golang/go/blob/go1.21.0/src/debug/buildinfo/buildinfo.go#L333
* for full details.
*
* Merely seeing a non-zero size will not actually do,
* though: he size must be at least `buildInfoSize`,
* i.e. 32, and we expect a UVarint (at least another
* byte) _and_ the bytes representing the string,
* which we expect to start with the letters "go" and
* continue with the Go version number.
*/
if (size < 32 + 1 + 2 + 1 || !pointer)
continue;
p = malloc(size);
if (!p || read_at(fd, p, pointer, size) < 0)
goto fail;
/*
* Look for the build information embedded by Go, see
* https://github.com/golang/go/blob/go1.21.0/src/debug/buildinfo/buildinfo.go#L165-L175
* for full details.
*
* Note: Go contains code to enforce alignment along a
* 16-byte boundary. In practice, no `.exe` has been
* observed that required any adjustment, therefore
* this here code skips that logic for simplicity.
*/
q = memmem(p, size - 18, "\xff Go buildinf:", 14);
if (!q)
goto fail;
/*
* Decode the build blob. For full details, see
* https://github.com/golang/go/blob/go1.21.0/src/debug/buildinfo/buildinfo.go#L177-L191
*
* Note: The `endianness` values observed in practice
* were always 2, therefore the complex logic to handle
* any other value is skipped for simplicty.
*/
if ((q[14] == 8 || q[14] == 4) && q[15] == 2) {
/*
* Only handle a Go version string with fewer
* than 128 characters, so the Go UVarint at
* q[32] that indicates the string's length must
* be only one byte (without the high bit set).
*/
if ((q[32] & 0x80) ||
!q[32] ||
(q + 33 + q[32] - p) > (ssize_t)size ||
q[32] + 1 > (ssize_t)go_version_size)
goto fail;
res = q[32];
memcpy(go_version, q + 33, res);
go_version[res] = '\0';
break;
}
}
}
fail:
free(p);
close(fd);
return res;
}
void win32_warn_about_git_lfs_on_windows7(int exit_code, const char *argv0)
{
char buffer[128], *git_lfs = NULL;
const char *p;
/*
* Git LFS v3.5.1 fails with an Access Violation on Windows 7; That
* would usually show up as an exit code 0xc0000005. For some reason
* (probably because at this point, we no longer have the _original_
* HANDLE that was returned by `CreateProcess()`) we observe other
* values like 0xb00 and 0x2 instead. Since the exact exit code
* seems to be inconsistent, we check for a non-zero exit status.
*/
if (exit_code == 0)
return;
if (GetVersion() >> 16 > 7601)
return; /* Warn only on Windows 7 or older */
if (!istarts_with(argv0, "git-lfs ") &&
strcasecmp(argv0, "git-lfs"))
return;
if (!(git_lfs = locate_in_PATH("git-lfs")))
return;
if (get_go_version(git_lfs, buffer, sizeof(buffer)) > 0 &&
skip_prefix(buffer, "go", &p) &&
versioncmp("1.21.0", p) <= 0)
warning("This program was built with Go v%s\n"
"i.e. without support for this Windows version:\n"
"\n\t%s\n"
"\n"
"To work around this, you can download and install a "
"working version from\n"
"\n"
"\thttps://github.com/git-lfs/git-lfs/releases/tag/"
"v3.4.1\n",
p, git_lfs);
free(git_lfs);
}
|