Skip to content

Commit 0baf0df

Browse files
committed
thread safe posix libc calls
1 parent e4b1d8e commit 0baf0df

File tree

4 files changed

+90
-18
lines changed

4 files changed

+90
-18
lines changed

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ errors.
3939

4040
[//]: # "Additions:"
4141

42+
- Standard posix calls on Linux and Windows are not all thread safe, switch to
43+
ugly but thread-safe extensions where affected in `nativesockets` and `times`.
44+
4245
- `setutils.symmetricDifference` along with its operator version
4346
`` setutils.`-+-` `` and in-place version `setutils.toggle` have been added
4447
to more efficiently calculate the symmetric difference of bitsets.

lib/posix/posix.nim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,14 @@ else:
10731073
proc gethostbyaddr*(a1: cstring, a2: cint, a3: cint): ptr Hostent {.
10741074
importc, header: "<netdb.h>".}
10751075
proc gethostbyname*(a1: cstring): ptr Hostent {.importc, header: "<netdb.h>".}
1076+
when defined(linux):
1077+
proc gethostbyaddr_r*(a1: pointer, a2: SockLen, a3: cint,
1078+
ret: ptr Hostent, buf: cstring, buflen: csize_t,
1079+
res: ptr ptr Hostent, h_errnop: ptr cint): cint {.
1080+
importc, header: "<netdb.h>".}
1081+
proc gethostbyname_r*(name: cstring, ret: ptr Hostent,
1082+
buf: cstring, buflen: csize_t, res: ptr ptr Hostent,
1083+
h_errnop: ptr cint): cint {.importc, header: "<netdb.h>".}
10761084
proc gethostent*(): ptr Hostent {.importc, header: "<netdb.h>".}
10771085

10781086
proc getnameinfo*(a1: ptr SockAddr, a2: SockLen,
@@ -1090,6 +1098,13 @@ proc getprotoent*(): ptr Protoent {.importc, header: "<netdb.h>".}
10901098
proc getservbyname*(a1, a2: cstring): ptr Servent {.importc, header: "<netdb.h>".}
10911099
proc getservbyport*(a1: cint, a2: cstring): ptr Servent {.
10921100
importc, header: "<netdb.h>".}
1101+
when defined(linux) and not defined(android):
1102+
proc getservbyname_r*(name, proto: cstring, resultBuf: ptr Servent,
1103+
buf: cstring, buflen: csize_t, res: ptr ptr Servent): cint {.
1104+
importc, header: "<netdb.h>".}
1105+
proc getservbyport_r*(port: cint, proto: cstring, resultBuf: ptr Servent,
1106+
buf: cstring, buflen: csize_t, res: ptr ptr Servent): cint {.
1107+
importc, header: "<netdb.h>".}
10931108
proc getservent*(): ptr Servent {.importc, header: "<netdb.h>".}
10941109

10951110
proc sethostent*(a1: cint) {.importc, header: "<netdb.h>".}

lib/pure/nativesockets.nim

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,22 @@ proc getProtoByName*(name: string): int {.since: (1, 3, 5).} =
215215
## Returns a protocol code from the database that matches the protocol `name`.
216216
when useWinVersion:
217217
let protoent = winlean.getprotobyname(name.cstring)
218+
elif defined(linux) and not defined(android):
219+
var protoent: ptr posix.Protoent
220+
{.emit: ";\n#ifdef __GLIBC__".}
221+
# glibc fix
222+
proc getprotobyname_r(name: cstring, resultBuf: ptr posix.Protoent,
223+
buf: cstring, buflen: csize_t,
224+
res: ptr ptr posix.Protoent): cint
225+
{.importc, header: "<netdb.h>".}
226+
var pe: posix.Protoent
227+
var buf: array[1024, char]
228+
discard getprotobyname_r(name.cstring, addr pe, cast[cstring](addr buf[0]),
229+
csize_t(buf.len), addr protoent)
230+
{.emit: ";\n#else".}
231+
# prevent musl regression
232+
protoent = posix.getprotobyname(name.cstring)
233+
{.emit: ";\n#endif".}
218234
else:
219235
let protoent = posix.getprotobyname(name.cstring)
220236

@@ -371,6 +387,12 @@ when not useNimNetLite:
371387
## On posix this will search through the `/etc/services` file.
372388
when useWinVersion:
373389
var s = winlean.getservbyname(name, proto)
390+
elif defined(linux) and not defined(android):
391+
var se: posix.Servent
392+
var buf: array[1024, char]
393+
var s: ptr posix.Servent
394+
discard getservbyname_r(name.cstring, proto.cstring, addr se,
395+
cast[cstring](addr buf[0]), csize_t(buf.len), addr s)
374396
else:
375397
var s = posix.getservbyname(name, proto)
376398
if s == nil: raiseOSError(osLastError(), "Service not found.")
@@ -389,6 +411,12 @@ when not useNimNetLite:
389411
## On posix this will search through the `/etc/services` file.
390412
when useWinVersion:
391413
var s = winlean.getservbyport(uint16(port).cint, proto)
414+
elif defined(linux) and not defined(android):
415+
var se: posix.Servent
416+
var buf: array[1024, char]
417+
var s: ptr posix.Servent
418+
discard getservbyport_r(uint16(port).cint, proto.cstring, addr se,
419+
cast[cstring](addr buf[0]), csize_t(buf.len), addr s)
392420
else:
393421
var s = posix.getservbyport(uint16(port).cint, proto)
394422
if s == nil: raiseOSError(osLastError(), "Service not found.")
@@ -424,14 +452,26 @@ when not useNimNetLite:
424452
var s = winlean.gethostbyaddr(cast[ptr InAddr](myAddr), addrLen.cuint,
425453
cint(family))
426454
if s == nil: raiseOSError(osLastError())
455+
elif defined(linux):
456+
var he: posix.Hostent
457+
var buf: array[4096, char]
458+
var h_errnop: cint
459+
var s: ptr posix.Hostent
460+
when defined(android4):
461+
discard gethostbyaddr_r(cast[cstring](myAddr), addrLen.SockLen,
462+
cint(family), addr he,
463+
cast[cstring](addr buf[0]), csize_t(buf.len),
464+
addr s, addr h_errnop)
465+
else:
466+
discard gethostbyaddr_r(myAddr, addrLen.SockLen,
467+
cint(family), addr he,
468+
cast[cstring](addr buf[0]), csize_t(buf.len),
469+
addr s, addr h_errnop)
470+
if s == nil:
471+
raiseOSError(osLastError(), $hstrerror(h_errnop))
427472
else:
428-
var s =
429-
when defined(android4):
430-
posix.gethostbyaddr(cast[cstring](myAddr), addrLen.cint,
431-
cint(family))
432-
else:
433-
posix.gethostbyaddr(myAddr, addrLen.SockLen,
434-
cint(family))
473+
# macOS: gethostbyaddr is thread-safe via TLS
474+
var s = posix.gethostbyaddr(myAddr, addrLen.SockLen, cint(family))
435475
if s == nil:
436476
raiseOSError(osLastError(), $hstrerror(h_errno))
437477

@@ -476,6 +516,14 @@ when not useNimNetLite:
476516
## This function will lookup the IP address of a hostname.
477517
when useWinVersion:
478518
var s = winlean.gethostbyname(name)
519+
elif defined(linux):
520+
var he: posix.Hostent
521+
var buf: array[4096, char]
522+
var h_errnop: cint
523+
var s: ptr posix.Hostent
524+
discard gethostbyname_r(name.cstring, addr he,
525+
cast[cstring](addr buf[0]), csize_t(buf.len),
526+
addr s, addr h_errnop)
479527
else:
480528
var s = posix.gethostbyname(name)
481529
if s == nil: raiseOSError(osLastError())

lib/pure/times.nim

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ elif defined(windows):
264264
tm_yday*: cint ## Day of year [0,365].
265265
tm_isdst*: cint ## Daylight Savings flag.
266266

267-
proc localtime(a1: var CTime): ptr Tm {.importc, header: "<time.h>", sideEffect.}
267+
# Windows CRT's localtime_s has reversed args vs C11
268+
proc localtime_s(a2: ptr Tm, a1: var CTime): cint {.importc, header: "<time.h>", sideEffect.}
268269

269270
type
270271
Month* = enum ## Represents a month. Note that the enum starts at `1`,
@@ -1322,20 +1323,25 @@ else:
13221323
when defined(windows):
13231324
if unix < 0:
13241325
var a = 0.CTime
1325-
let tmPtr = localtime(a)
1326-
if not tmPtr.isNil:
1327-
let tm = tmPtr[]
1328-
return ((0 - tm.toAdjUnix).int, false)
1329-
return (0, false)
1326+
var tm: Tm
1327+
if localtime_s(addr tm, a) != 0:
1328+
return (0, false)
1329+
return ((0 - tm.toAdjUnix).int, false)
13301330

13311331
# In case of a 32-bit time_t, we fallback to the closest available
13321332
# timezone information.
13331333
var a = clamp(unix, low(CTime).int64, high(CTime).int64).CTime
1334-
let tmPtr = localtime(a)
1335-
if not tmPtr.isNil:
1336-
let tm = tmPtr[]
1337-
return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0)
1338-
return (0, false)
1334+
var tm: Tm
1335+
when defined(windows):
1336+
if localtime_s(addr tm, a) != 0:
1337+
return (0, false)
1338+
else:
1339+
# localtime_r doesn't call tzset() implicitly unlike localtime().
1340+
# tzset() must be called before localtime_r to pick up TZ changes.
1341+
tzset()
1342+
if localtime_r(a, tm).isNil:
1343+
return (0, false)
1344+
return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0)
13391345

13401346
proc localZonedTimeFromTime(time: Time): ZonedTime {.gcsafe.} =
13411347
let (offset, dst) = getLocalOffsetAndDst(time.seconds)

0 commit comments

Comments
 (0)