From 3ac2c4636dd1054c3faf99c5ac5638be42597156 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 17 May 2026 09:45:11 +0300 Subject: [PATCH] builder, db.pg: fix linux cross compilation with libpq and extra C sources cc_linux_cross now pulls raw .c/.cpp/.S paths out of #flag values before the main `clang -c -o ` step (clang refuses -c with multiple inputs) and compiles each into its own object, hooked into the linker step. Linker arg order also reshuffled so user libs precede -lssl/-lcrypto. The bundled linuxroot ships libpq.a but no libpgcommon.a/libpgport.a, so add thirdparty/pg/pgport_stubs.c covering the frontend symbols (strlcpy, pg_snprintf, pg_b64_*, scram_*, pg_md5_encrypt, encoding helpers) by delegating to libc / OpenSSL, and wire it via `#flag` under `$if cross_compile ? && linux` in vlib/db/pg/pg.c.v. Unblocks e.g. gitly cross compilation with -cc clang -d use_openssl -os linux. --- .github/workflows/cross_ci.yml | 8 + thirdparty/.gitignore | 3 +- thirdparty/pg/pgport_stubs.c | 671 +++++++++++++++++++++++++++++++++ vlib/db/pg/pg.c.v | 4 + vlib/v/builder/cc.v | 67 +++- 5 files changed, 745 insertions(+), 8 deletions(-) create mode 100644 thirdparty/pg/pgport_stubs.c diff --git a/.github/workflows/cross_ci.yml b/.github/workflows/cross_ci.yml index fecda31c1..2bf27d545 100644 --- a/.github/workflows/cross_ci.yml +++ b/.github/workflows/cross_ci.yml @@ -49,6 +49,14 @@ jobs: - name: Cross-compilation of veb app to Linux with -d use_openssl run: ./v -d use_openssl -os linux examples/veb/veb_example.v + - name: Cross-compilation of vlang/gitly to Linux with -d use_openssl + run: | + ./v retry -- ./v install markdown + ./v retry -- ./v install pcre + ./v retry -- git clone --depth 1 https://github.com/vlang/gitly /tmp/gitly + ./v -d use_openssl -os linux /tmp/gitly + file /tmp/gitly/gitly + - name: Cross-compilation to Windows run: | ./v -os windows cmd/v diff --git a/thirdparty/.gitignore b/thirdparty/.gitignore index c96446781..b3a527419 100644 --- a/thirdparty/.gitignore +++ b/thirdparty/.gitignore @@ -6,6 +6,7 @@ sdl2/ SDL2_image/ SDL2_mixer/ SDL2_ttf/ -pg/ +pg/* +!pg/pgport_stubs.c tcc/ sqlite/ diff --git a/thirdparty/pg/pgport_stubs.c b/thirdparty/pg/pgport_stubs.c new file mode 100644 index 000000000..2cffd3af3 --- /dev/null +++ b/thirdparty/pg/pgport_stubs.c @@ -0,0 +1,671 @@ +/* + * Minimal libpgport/libpgcommon stubs for V's `db.pg` cross compilation to + * Linux. The bundled `linuxroot` sysroot ships a static `libpq.a` but no + * `libpgcommon.a` / `libpgport.a`, so a number of symbols referenced by + * libpq stay unresolved at link time. The implementations here cover only + * the entry points actually referenced by the libpq.a frontend (verified + * via `ld.lld --error-limit=0`), forwarding to libc / OpenSSL equivalents. + * They are NOT a full replacement for libpgport / libpgcommon: SASLprep + * is a no-op, encoding tables only know the name list, etc. Enough to + * connect to a Postgres server using SCRAM-SHA-256 and exchange queries + * in SQL_ASCII / UTF8. + * + * Linked only via `$if cross_compile ? && linux` in vlib/db/pg/pg.c.v. + * The linuxroot sysroot lacks some POSIX headers (e.g. pwd.h), so the few + * libc entry points used here are declared inline instead of pulled from + * system headers. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Forward declarations to avoid relying on headers the linuxroot sysroot + * doesn't ship (pwd.h, full netdb.h getaddrinfo/getnameinfo prototypes, + * sys/un.h ucred). */ +struct passwd; +struct addrinfo; +struct sockaddr; + +extern int getpwuid_r(unsigned int uid, struct passwd *pwd, char *buf, + size_t buflen, struct passwd **result); +extern int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +extern void freeaddrinfo(struct addrinfo *res); +extern int getnameinfo(const struct sockaddr *sa, unsigned int salen, char *host, + unsigned int hostlen, char *serv, unsigned int servlen, + int flags); +extern const char *inet_ntop(int af, const void *src, char *dst, unsigned int size); + +/* OpenSSL — already linked via -lssl -lcrypto. We declare what we need so we + * don't depend on being on the include search path. */ +typedef struct evp_md_st EVP_MD; +typedef struct hmac_ctx_st HMAC_CTX; +typedef struct engine_st ENGINE; +extern const EVP_MD *EVP_sha224(void); +extern const EVP_MD *EVP_sha256(void); +extern const EVP_MD *EVP_sha384(void); +extern const EVP_MD *EVP_sha512(void); +extern const EVP_MD *EVP_md5(void); +extern const EVP_MD *EVP_sha1(void); +extern HMAC_CTX *HMAC_CTX_new(void); +extern void HMAC_CTX_free(HMAC_CTX *ctx); +extern int HMAC_Init_ex(HMAC_CTX *ctx, const void *key, int len, + const EVP_MD *md, ENGINE *impl); +extern int HMAC_Update(HMAC_CTX *ctx, const unsigned char *data, size_t len); +extern int HMAC_Final(HMAC_CTX *ctx, unsigned char *md, unsigned int *len); +extern int RAND_bytes(unsigned char *buf, int num); + +size_t strlcpy(char *dst, const char *src, size_t siz) { + const char *s = src; + size_t n = siz; + if (n != 0) { + while (--n != 0) { + if ((*dst++ = *s++) == '\0') { + break; + } + } + } + if (n == 0) { + if (siz != 0) { + *dst = '\0'; + } + while (*s++) { + } + } + return (size_t)(s - src - 1); +} + +int pg_vsnprintf(char *str, size_t count, const char *fmt, va_list args) { + return vsnprintf(str, count, fmt, args); +} + +int pg_snprintf(char *str, size_t count, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(str, count, fmt, ap); + va_end(ap); + return n; +} + +int pg_sprintf(char *str, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsprintf(str, fmt, ap); + va_end(ap); + return n; +} + +int pg_vfprintf(FILE *stream, const char *fmt, va_list args) { + return vfprintf(stream, fmt, args); +} + +int pg_fprintf(FILE *stream, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vfprintf(stream, fmt, ap); + va_end(ap); + return n; +} + +int pg_printf(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vprintf(fmt, ap); + va_end(ap); + return n; +} + +int pg_strcasecmp(const char *s1, const char *s2) { + return strcasecmp(s1, s2); +} + +int pg_strncasecmp(const char *s1, const char *s2, size_t n) { + return strncasecmp(s1, s2, n); +} + +unsigned char pg_tolower(unsigned char ch) { + if (ch >= 'A' && ch <= 'Z') { + return ch + ('a' - 'A'); + } + return ch; +} + +unsigned char pg_toupper(unsigned char ch) { + if (ch >= 'a' && ch <= 'z') { + return ch - ('a' - 'A'); + } + return ch; +} + +int pg_strip_crlf(char *str) { + int len = (int)strlen(str); + while (len > 0 && (str[len - 1] == '\n' || str[len - 1] == '\r')) { + str[--len] = '\0'; + } + return len; +} + +char *pg_strerror_r(int errnum, char *buf, size_t buflen) { + const char *s = strerror(errnum); + if (s == NULL) { + snprintf(buf, buflen, "errno %d", errnum); + } else { + strncpy(buf, s, buflen); + if (buflen > 0) { + buf[buflen - 1] = '\0'; + } + } + return buf; +} + +int pqGetpwuid(unsigned int uid, struct passwd *resbuf, char *buf, size_t buflen, + struct passwd **result) { + return getpwuid_r(uid, resbuf, buf, buflen, result); +} + +int pg_getaddrinfo_all(const char *hostname, const char *servname, + const struct addrinfo *hint, struct addrinfo **result) { + return getaddrinfo(hostname, servname, hint, result); +} + +void pg_freeaddrinfo_all(int hint_ai_family, struct addrinfo *ai) { + (void)hint_ai_family; + if (ai != NULL) { + freeaddrinfo(ai); + } +} + +int pg_getnameinfo_all(const void *addr, int salen, char *node, size_t nodelen, + char *service, size_t servicelen, int flags) { + return getnameinfo((const struct sockaddr *)addr, (unsigned int)salen, node, + (unsigned int)nodelen, service, (unsigned int)servicelen, + flags); +} + +int pg_set_noblock(int sock) { + int flags = fcntl(sock, F_GETFL); + if (flags < 0) { + return 0; + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { + return 0; + } + return 1; +} + +/* Linux-only: getpeereid via SO_PEERCRED. */ +struct pg_ucred_compat { unsigned int pid; unsigned int uid; unsigned int gid; }; +#ifndef SO_PEERCRED +#define SO_PEERCRED 17 +#endif + +int getpeereid(int sock, unsigned int *euid, unsigned int *egid) { + struct pg_ucred_compat cred; + unsigned int len = sizeof(cred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) { + return -1; + } + if (euid != NULL) { + *euid = cred.uid; + } + if (egid != NULL) { + *egid = cred.gid; + } + return 0; +} + +const char *pg_inet_net_ntop(int af, const void *src, int bits, char *dst, + size_t size) { + (void)bits; + return inet_ntop(af, src, dst, (unsigned int)size); +} + +/* SASLprep: a real implementation normalizes Unicode per RFC 4013. The + * frontend uses it on passwords before SCRAM hashing; if we just pass the + * raw bytes through, ASCII-only passwords still hash to the same value as + * any compliant client would produce. Non-ASCII passwords may fail to + * authenticate. PG_SASLPREP_SUCCESS == 0 in PostgreSQL. */ +int pg_saslprep(const char *input, char **output) { + size_t n = strlen(input); + char *copy = (char *)malloc(n + 1); + if (copy == NULL) { + return -1; /* PG_SASLPREP_OOM */ + } + memcpy(copy, input, n + 1); + *output = copy; + return 0; +} + +int pg_strong_random(void *buf, size_t len) { + if (RAND_bytes((unsigned char *)buf, (int)len) == 1) { + return 1; + } + return 0; +} + +/* Base64 codec: standard alphabet, identical layout to PostgreSQL's + * src/common/base64.c so libpq's helpers (which expect specific overflow + * semantics) behave the same. */ +static const char b64_alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const signed char b64_lookup[256] = { + [0 ... 255] = -1, + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, + ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, + ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, + ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, + ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, + ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, + ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, + ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, + ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, + ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63, +}; + +int pg_b64_enc_len(int srclen) { + return ((srclen + 2) / 3) * 4; +} + +int pg_b64_dec_len(int srclen) { + return (srclen / 4) * 3; +} + +int pg_b64_encode(const char *src, int len, char *dst, int dstlen) { + int i = 0, j = 0; + while (i < len) { + unsigned int b0 = (unsigned char)src[i++]; + unsigned int b1 = (i < len) ? (unsigned char)src[i++] : 0; + unsigned int b2 = (i < len) ? (unsigned char)src[i++] : 0; + int rem = (i > len) ? (i - len) : 0; + if (j + 4 > dstlen) { + return -1; + } + dst[j++] = b64_alphabet[b0 >> 2]; + dst[j++] = b64_alphabet[((b0 & 0x03) << 4) | (b1 >> 4)]; + dst[j++] = (rem >= 2) ? '=' : b64_alphabet[((b1 & 0x0f) << 2) | (b2 >> 6)]; + dst[j++] = (rem >= 1) ? '=' : b64_alphabet[b2 & 0x3f]; + } + return j; +} + +int pg_b64_decode(const char *src, int len, char *dst, int dstlen) { + int i = 0, j = 0; + while (i < len) { + while (i < len && (src[i] == '\n' || src[i] == '\r' || src[i] == ' ' || + src[i] == '\t')) { + i++; + } + if (i >= len) { + break; + } + signed char c0 = b64_lookup[(unsigned char)src[i++]]; + if (c0 < 0) { + return -1; + } + while (i < len && (src[i] == '\n' || src[i] == '\r' || src[i] == ' ' || + src[i] == '\t')) { + i++; + } + if (i >= len) { + return -1; + } + signed char c1 = b64_lookup[(unsigned char)src[i++]]; + if (c1 < 0) { + return -1; + } + while (i < len && (src[i] == '\n' || src[i] == '\r' || src[i] == ' ' || + src[i] == '\t')) { + i++; + } + signed char c2 = -1, c3 = -1; + char raw2 = 0, raw3 = 0; + if (i < len) { + raw2 = src[i++]; + if (raw2 != '=') { + c2 = b64_lookup[(unsigned char)raw2]; + if (c2 < 0) { + return -1; + } + } + } + while (i < len && (src[i] == '\n' || src[i] == '\r' || src[i] == ' ' || + src[i] == '\t')) { + i++; + } + if (i < len) { + raw3 = src[i++]; + if (raw3 != '=') { + c3 = b64_lookup[(unsigned char)raw3]; + if (c3 < 0) { + return -1; + } + } + } + if (j >= dstlen) { + return -1; + } + dst[j++] = (char)((c0 << 2) | ((c1 & 0x30) >> 4)); + if (raw2 == '=') { + break; + } + if (j >= dstlen) { + return -1; + } + dst[j++] = (char)(((c1 & 0x0f) << 4) | ((c2 & 0x3c) >> 2)); + if (raw3 == '=') { + break; + } + if (j >= dstlen) { + return -1; + } + dst[j++] = (char)(((c2 & 0x03) << 6) | c3); + } + return j; +} + +/* pg_hmac: thin wrapper over OpenSSL HMAC_CTX. PG's pg_cryptohash_type: + * 0 PG_MD5, 1 PG_SHA1, 2 PG_SHA224, 3 PG_SHA256, 4 PG_SHA384, 5 PG_SHA512 */ +struct pg_hmac_ctx { + HMAC_CTX *ctx; + const EVP_MD *md; + int type; + int error; +}; + +struct pg_hmac_ctx *pg_hmac_create(int type) { + const EVP_MD *md = NULL; + switch (type) { + case 0: md = EVP_md5(); break; + case 1: md = EVP_sha1(); break; + case 2: md = EVP_sha224(); break; + case 3: md = EVP_sha256(); break; + case 4: md = EVP_sha384(); break; + case 5: md = EVP_sha512(); break; + default: return NULL; + } + if (md == NULL) { + return NULL; + } + struct pg_hmac_ctx *c = (struct pg_hmac_ctx *)malloc(sizeof(*c)); + if (c == NULL) { + return NULL; + } + c->md = md; + c->type = type; + c->error = 0; + c->ctx = HMAC_CTX_new(); + if (c->ctx == NULL) { + free(c); + return NULL; + } + return c; +} + +int pg_hmac_init(struct pg_hmac_ctx *ctx, const unsigned char *key, size_t len) { + if (ctx == NULL) { + return -1; + } + if (HMAC_Init_ex(ctx->ctx, key, (int)len, ctx->md, NULL) != 1) { + ctx->error = 1; + return -1; + } + return 0; +} + +int pg_hmac_update(struct pg_hmac_ctx *ctx, const unsigned char *data, size_t len) { + if (ctx == NULL) { + return -1; + } + if (HMAC_Update(ctx->ctx, data, len) != 1) { + ctx->error = 1; + return -1; + } + return 0; +} + +int pg_hmac_final(struct pg_hmac_ctx *ctx, unsigned char *dest, size_t len) { + if (ctx == NULL) { + return -1; + } + unsigned int outlen = (unsigned int)len; + if (HMAC_Final(ctx->ctx, dest, &outlen) != 1) { + ctx->error = 1; + return -1; + } + return 0; +} + +void pg_hmac_free(struct pg_hmac_ctx *ctx) { + if (ctx == NULL) { + return; + } + if (ctx->ctx != NULL) { + HMAC_CTX_free(ctx->ctx); + } + free(ctx); +} + +const char *pg_hmac_error(struct pg_hmac_ctx *ctx) { + if (ctx == NULL) { + return "out of memory"; + } + if (ctx->error) { + return "HMAC error"; + } + return ""; +} + +/* SCRAM-SHA-256 helpers. Signatures match PG 14 src/common/scram-common.c. + * SCRAM_KEY_LEN is 32 (SHA-256 output). */ +extern int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, + const unsigned char *salt, int saltlen, int iter, + const EVP_MD *digest, int keylen, + unsigned char *out); + +static int scram_hmac_one(const unsigned char *key, const char *msg, + unsigned char *result, const char **errstr) { + struct pg_hmac_ctx *ctx = pg_hmac_create(3 /* PG_SHA256 */); + if (ctx == NULL) { + if (errstr != NULL) { + *errstr = "out of memory"; + } + return -1; + } + if (pg_hmac_init(ctx, key, 32) < 0 || + pg_hmac_update(ctx, (const unsigned char *)msg, strlen(msg)) < 0 || + pg_hmac_final(ctx, result, 32) < 0) { + if (errstr != NULL) { + *errstr = pg_hmac_error(ctx); + } + pg_hmac_free(ctx); + return -1; + } + pg_hmac_free(ctx); + return 0; +} + +int scram_ServerKey(const unsigned char *salted_password, unsigned char *result, + const char **errstr) { + return scram_hmac_one(salted_password, "Server Key", result, errstr); +} + +int scram_ClientKey(const unsigned char *salted_password, unsigned char *result, + const char **errstr) { + return scram_hmac_one(salted_password, "Client Key", result, errstr); +} + +/* SHA-256 of input → result (32 bytes). */ +extern unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md); +int scram_H(const unsigned char *input, int len, unsigned char *result, + const char **errstr) { + if (SHA256(input, (size_t)len, result) == NULL) { + if (errstr != NULL) { + *errstr = "SHA256 failed"; + } + return -1; + } + return 0; +} + +/* PBKDF2-HMAC-SHA256(password, salt, iterations) → 32-byte salted password. */ +int scram_SaltedPassword(const char *password, const char *salt, int saltlen, + int iterations, unsigned char *result, + const char **errstr) { + if (PKCS5_PBKDF2_HMAC(password, (int)strlen(password), + (const unsigned char *)salt, saltlen, iterations, + EVP_sha256(), 32, result) != 1) { + if (errstr != NULL) { + *errstr = "PBKDF2 failed"; + } + return -1; + } + return 0; +} + +/* scram_build_secret is server-side; the archive references it because some + * objects in libpq.a were compiled with both frontend and backend sections + * sharing scram-common.c. Provide a stub that returns NULL — the frontend + * code path that actually calls this would be unreachable. */ +char *scram_build_secret(const char *salt, int saltlen, int iterations, + const char *password, const char **errstr) { + (void)salt; + (void)saltlen; + (void)iterations; + (void)password; + if (errstr != NULL) { + *errstr = "scram_build_secret not implemented in frontend stubs"; + } + return NULL; +} + +/* pg_md5_encrypt: legacy "md5"-prefixed MD5(password || username) hash. */ +extern unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md); +int pg_md5_encrypt(const char *passwd, const char *salt, size_t salt_len, + char *buf) { + static const char hex[] = "0123456789abcdef"; + size_t passwd_len = strlen(passwd); + unsigned char digest[16]; + unsigned char *tmp = (unsigned char *)malloc(passwd_len + salt_len); + if (tmp == NULL) { + return 0; + } + memcpy(tmp, passwd, passwd_len); + memcpy(tmp + passwd_len, salt, salt_len); + if (MD5(tmp, passwd_len + salt_len, digest) == NULL) { + free(tmp); + return 0; + } + free(tmp); + buf[0] = 'm'; + buf[1] = 'd'; + buf[2] = '5'; + for (int i = 0; i < 16; i++) { + buf[3 + 2 * i] = hex[digest[i] >> 4]; + buf[3 + 2 * i + 1] = hex[digest[i] & 0x0f]; + } + buf[35] = '\0'; + return 1; +} + +/* Encoding helpers: the linuxroot libpq.a is built without libpgcommon, so the + * client-side encoding tables are absent. Returning sensible defaults keeps + * basic libpq usage (connect, query in SQL_ASCII / UTF8) working. */ + +int pg_get_encoding_from_locale(const char *ctype, int write_message) { + (void)ctype; + (void)write_message; + return -1; +} + +static const char *const pg_encoding_names[] = { + "SQL_ASCII", "EUC_JP", "EUC_CN", "EUC_KR", "EUC_TW", "EUC_JIS_2004", + "UTF8", "MULE_INTERNAL", "LATIN1", "LATIN2", "LATIN3", "LATIN4", "LATIN5", + "LATIN6", "LATIN7", "LATIN8", "LATIN9", "LATIN10", "WIN1256", "WIN1258", + "WIN866", "WIN874", "KOI8R", "WIN1251", "WIN1252", "ISO_8859_5", "ISO_8859_6", + "ISO_8859_7", "ISO_8859_8", "WIN1250", "WIN1253", "WIN1254", "WIN1255", + "WIN1257", "KOI8U", "SJIS", "BIG5", "GBK", "UHC", "GB18030", "JOHAB", + "SHIFT_JIS_2004", +}; + +const char *pg_encoding_to_char(int encoding) { + const int n = (int)(sizeof(pg_encoding_names) / sizeof(pg_encoding_names[0])); + if (encoding < 0 || encoding >= n) { + return "SQL_ASCII"; + } + return pg_encoding_names[encoding]; +} + +int pg_char_to_encoding(const char *name) { + const int n = (int)(sizeof(pg_encoding_names) / sizeof(pg_encoding_names[0])); + if (name == NULL) { + return -1; + } + for (int i = 0; i < n; i++) { + if (strcasecmp(name, pg_encoding_names[i]) == 0) { + return i; + } + } + return -1; +} + +int pg_valid_server_encoding_id(int encoding) { + return encoding == 0 || encoding == 6; +} + +/* Multibyte helpers. Treat SQL_ASCII (0) and UTF8 (6) properly; for other + * encodings, fall back to UTF8-shaped behaviour. Good enough for libpq's + * frontend encoding probes, but not a substitute for libpgcommon's tables. */ +int pg_encoding_max_length(int encoding) { + if (encoding == 0 /* SQL_ASCII */) { + return 1; + } + return 4; /* UTF-8 worst case */ +} + +int pg_encoding_mblen(int encoding, const char *mbstr) { + unsigned char c = (unsigned char)*mbstr; + if (encoding == 0 /* SQL_ASCII */) { + return 1; + } + if (c < 0x80) { + return 1; + } + if ((c & 0xe0) == 0xc0) { + return 2; + } + if ((c & 0xf0) == 0xe0) { + return 3; + } + if ((c & 0xf8) == 0xf0) { + return 4; + } + return 1; +} + +int pg_encoding_dsplen(int encoding, const char *mbstr) { + (void)encoding; + unsigned char c = (unsigned char)*mbstr; + if (c == 0) { + return 0; + } + if (c < 0x20 || c == 0x7f) { + return -1; /* control char: caller decides */ + } + return 1; +} + +/* libpgcommon link canary: signals that libpq was linked with the matching + * libpgcommon flavour. We only have the frontend, so always report frontend. */ +int pg_link_canary_is_frontend(void) { + return 1; +} diff --git a/vlib/db/pg/pg.c.v b/vlib/db/pg/pg.c.v index cd87eb149..1939f453c 100644 --- a/vlib/db/pg/pg.c.v +++ b/vlib/db/pg/pg.c.v @@ -41,6 +41,10 @@ $if cross_compile ? && linux { #include //#flag -lpq // libpq.a is located in LINUXROOT/lib/x86_64-linux-gnu/libpq.a + // The bundled linuxroot ships libpq.a but no libpgcommon.a / libpgport.a, + // so libpq's references to pg_snprintf, strlcpy, pg_freeaddrinfo_all etc. + // are unresolved at link time. Provide minimal stubs that delegate to libc. + #flag @VEXEROOT/thirdparty/pg/pgport_stubs.c } $else { // PostgreSQL Source Code // https://doxygen.postgresql.org/libpq-fe_8h.html diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 8a9158ee1..b918c3116 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -1853,22 +1853,66 @@ fn (mut b Builder) cc_linux_cross() { } cflags := b.get_os_cflags() defines, others, libs := cflags.defines_others_libs() + // Some modules pass a raw `#flag /path/to/file.c` to add an additional + // source file to the compile step (e.g. gitly's markdown module uses this + // for md4c-lib.c). The native build line just appends them to the main + // `clang ... main.c` invocation, but the cross-compile path uses + // `clang -c -o `, and clang refuses to combine `-c` with + // multiple inputs ("cannot specify -o when generating multiple output + // files"). Pull those out, compile each separately into its own .o, and + // hand the resulting objects to the linker step. + mut other_flags := []string{cap: others.len} + mut extra_sources := []string{} + for opt in others { + unq := opt.trim('"').trim("'") + ext := os.file_ext(unq).to_lower() + if ext in ['.c', '.cpp', '.cc', '.cxx', '.s'] && os.is_file(unq) { + extra_sources << unq + } else { + other_flags << opt + } + } + mut cc_name := b.pref.ccompiler + mut out_name := b.pref.out_name + $if windows { + out_name = out_name.trim_string_right('.exe') + } + mut extra_objs := []string{cap: extra_sources.len} + for src in extra_sources { + src_obj := os.join_path(os.vtmp_dir(), os.file_name(src) + '.' + + linux_cross_target.lib_dir + '.o') + mut src_args := []string{cap: 16} + src_args << '-w' + src_args << '-fPIC' + src_args << '-target ${linux_cross_target.triple}' + src_args << defines + src_args << '-I ${os.quoted_path('${sysroot}/include')}' + src_args << other_flags + src_args << '-o ${os.quoted_path(src_obj)}' + src_args << '-c ${os.quoted_path(src)}' + src_cmd := '${b.quote_compiler_name(cc_name)} ' + src_args.join(' ') + if b.pref.show_cc { + println(src_cmd) + } + src_res := os.execute(src_cmd) + if src_res.exit_code != 0 { + println('Cross compilation for Linux failed (extra source ${src}).') + verror(src_res.output) + return + } + extra_objs << src_obj + } mut cc_args := []string{cap: 20} cc_args << '-w' cc_args << '-fPIC' cc_args << '-target ${linux_cross_target.triple}' cc_args << defines cc_args << '-I ${os.quoted_path('${sysroot}/include')} ' - cc_args << others + cc_args << other_flags cc_args << '-o ${os.quoted_path(obj_file)}' cc_args << '-c ${os.quoted_path(b.out_name_c)}' cc_args << libs b.dump_c_options(cc_args) - mut cc_name := b.pref.ccompiler - mut out_name := b.pref.out_name - $if windows { - out_name = out_name.trim_string_right('.exe') - } cc_cmd := '${b.quote_compiler_name(cc_name)} ' + cc_args.join(' ') if b.pref.show_cc { println(cc_cmd) @@ -1909,6 +1953,16 @@ fn (mut b Builder) cc_linux_cross() { os.quoted_path('${sysroot}/crt1.o'), os.quoted_path('${sysroot}/crti.o'), os.quoted_path(obj_file), + ] + for eobj in extra_objs { + linker_args << os.quoted_path(eobj) + } + // User-defined libraries (e.g. `-lpq` from db.pg) and extra object files + // must come before the system libraries they depend on. ld.lld resolves + // references left-to-right, so libpq.a needs to be encountered before + // -lssl/-lcrypto, otherwise its references to SSL_*/EVP_* stay unresolved. + linker_args << cflags.c_options_only_object_files() + linker_args << [ '-lc', '-lcrypto', '-lssl', @@ -1917,7 +1971,6 @@ fn (mut b Builder) cc_linux_cross() { '-lm', '-ldl', ] - linker_args << cflags.c_options_only_object_files() if os.exists(builtins_obj) { linker_args << os.quoted_path(builtins_obj) } -- 2.39.5