1 /*
2  * SRT - Secure, Reliable, Transport
3  * Copyright (c) 2018 Haivision Systems Inc.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  */
10 
11 // Implementation file for srt_compat.h
12 /*****************************************************************************
13 written by
14    Haivision Systems Inc.
15  *****************************************************************************/
16 
17 // Prevents from misconfiguration through preprocessor.
18 
19 #include "platform_sys.h"
20 
21 #include <srt_compat.h>
22 
23 #include <string.h>
24 #include <stdio.h>
25 #include <errno.h>
26 #if defined(__unix__) && !defined(BSD) && !defined(SUNOS)
27 #include <features.h>
28 #endif
29 
30 #if defined(_WIN32)
31 #define WIN32_LEAN_AND_MEAN
32 #include <windows.h>
33 #endif
34 
35 
SysStrError_Fallback(int errnum,char * buf,size_t buflen)36 static const char* SysStrError_Fallback(int errnum, char* buf, size_t buflen)
37 {
38 #if defined(_MSC_VER) && _MSC_VER < 1900
39     _snprintf(buf, buflen - 1, "ERROR CODE %d", errnum);
40     buf[buflen - 1] = '\0';
41 #else
42     snprintf(buf, buflen, "ERROR CODE %d", errnum);
43 #endif
44     return buf;
45 }
46 
47 // This function is a portable and thread-safe version of `strerror`.
48 // It requires a user-supplied buffer to store the message. The returned
49 // value is always equal to the given buffer pointer. If the system
50 // error message is longer than the given buflen, it will be trimmed.
51 // When the error code is incorrect for the given error message function,
52 // a fallback message will be returned, either as returned by the underlying
53 // function, or crafted by this function as a response to error in an
54 // underlying function.
SysStrError(int errnum,char * buf,size_t buflen)55 extern const char * SysStrError(int errnum, char * buf, size_t buflen)
56 {
57     if (buf == NULL || buflen < 4) // Required to put ??? into it as a fallback
58     {
59         errno = EFAULT;
60         return buf;
61     }
62 
63     buf[0] = '\0';
64 
65 #if defined(_WIN32)
66     const char* lpMsgBuf;
67 
68     // Note: Intentionally the "fixed char size" types are used despite using
69     // character size dependent FormatMessage (instead of FormatMessageA) so that
70     // your compilation fails when you use wide characters.
71     // The problem is that when TCHAR != char, then the buffer written this way
72     // would have to be converted to ASCII, not just copied by strncpy.
73     FormatMessage(0
74             | FORMAT_MESSAGE_ALLOCATE_BUFFER
75             | FORMAT_MESSAGE_FROM_SYSTEM
76             | FORMAT_MESSAGE_IGNORE_INSERTS,
77             NULL, // no lpSource
78             errnum, // dwMessageId (as controlled by FORMAT_MESSAGE_FROM_SYSTEM)
79             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
80             // This below parameter normally should contain a pointer to an allocated buffer,
81             // and this way it's LPTSTR. But when FORMAT_MESSAGE_ALLOCATE_BUFFER, then it is
82             // expected to be a the value of LPTSTR* type, converted to LPTSTR, that designates
83             // a pointer to a variable of type LPTSTR, to which the newly allocated buffer is
84             // assigned. This buffer should be freed afterwards using LocalFree().
85             (LPSTR)&lpMsgBuf,
86             0, NULL);
87 
88     if (lpMsgBuf)
89     {
90         strncpy(buf, lpMsgBuf, buflen-1);
91         buf[buflen-1] = 0;
92         LocalFree((HLOCAL)lpMsgBuf);
93     }
94     else
95     {
96         SysStrError_Fallback(errnum, buf, buflen);
97     }
98 
99     return buf;
100 
101 #elif (!defined(__GNU_LIBRARY__) && !defined(__GLIBC__) )  \
102     || (( (_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600)) && ! _GNU_SOURCE )
103     // POSIX/XSI-compliant version.
104     // Overall general POSIX version: returns status.
105     // 0 for success, otherwise it's:
106     // - possibly -1 and the error code is in ::errno
107     // - possibly the error code itself
108     // The details of the errror are not interesting; simply
109     // craft a fallback message in this case.
110     if (strerror_r(errnum, buf, buflen) != 0)
111     {
112         return SysStrError_Fallback(errnum, buf, buflen);
113     }
114     return buf;
115 #else
116     // GLIBC is non-standard under these conditions.
117     // GNU version: returns the pointer to the message.
118     // This is either equal to the local buffer (buf)
119     // or some system-wide (constant) storage. To maintain
120     // stability of the API, this overall function shall
121     // always return the local buffer and the message in
122     // this buffer - so these cases should be distinguished
123     // and the internal storage copied to the buffer.
124 
125     char * gnu_buffer = strerror_r(errnum, buf, buflen);
126     if (!gnu_buffer)
127     {
128         // This should never happen, so just a paranoid check
129         return SysStrError_Fallback(errnum, buf, buflen);
130     }
131 
132     // If they are the same, the message is already copied
133     // (and it's usually a "fallback message" for an error case).
134     if (gnu_buffer != buf)
135     {
136         strncpy(buf, gnu_buffer, buflen-1);
137         buf[buflen-1] = 0; // guarantee what strncpy doesn't
138     }
139 
140     return buf;
141 #endif
142 }
143