Secure Oldies II: Refactoring and Make it Compile on macOS and Linux.
Windows and Unix is not THAT different

This second article is an continuation of my first article about building a simple HTTP client which runs on Windows 2000 onwards.
Before going backward to the like of Windows 9x, Me, and NT 4.0 we're going lateral first. We'd try to make our code works on latest MacOS and Linux.
The Tale of Portability
A portable code is a code that can be compiled and run on many different platforms. For now, our code only runs on Windows only. Specifically Windows NT 5.0 onwards. As I've said on my first article on this series, Sockets API is everywhere including macOS and Linux. So let's make it happen.
Windows implementations of Winsock is largely based on BSD socket. However, there are some difference between it and BSD socket usually used in Unix or Unix-like operating systems like Linux and macOS.
- The most visible is that the existence of
WSAStartup()andWSACleanup()function which is mandatory on Windows. - Windows uses
closesocket()to close a socket rather than typicalclose()function available on POSIX compatible operating systems. SOCKETon Windows is not file descriptor, it's private data structure specific to Windows and must be used by WinSock functions only and is not interchangeable with file descriptors withintdata type.WSAGetLastError()is used to get last error on Winsock code rather thanerrnotypical in Unix systems.
Because of those differences, we'd need to isolate platform specific codes.
Isolating Platform specific codes.
First of all we'd need to refactor out the header inclusion between Windows and Unix Headers. For that I'd create a file with #ifdef for each platform. I'll name it platform.h. The contents of this header is only platform specific header inclusion.
#ifndef OTLS_PLATFORM_H
#define OTLS_PLATFORM_H 1
#if defined (WIN32)
#define WIN32_LEAN_AND_MEAN // (1)
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#elif defined(__linux__) || defined (__unix__) || defined (__APPLE__)
#define closesocket close // (2)
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#else
#error "Unsupported platform"
#endif
// .. more code here ..
#endif
We add needed headers and add #ifdef for WIN32 :
- This part will be compiled if we're targeting Windows
- This part will be compiled if we're targeting Unix/Linux/macOS
- We redefine
SOCKETtointbecause in Unix, socket is file descriptor. INVALID_SOCKETandSOCKET_ERRORwill be-1as this is the value on POSIX API for failure.<errno.h>provideserrnovariable which similar toWSAGetLastError().<netdb.h>providesstruct in_addrand functiongethostbyname().<netinet/in.h>providesstruct sockaddr_inand constants for internet protocol families.<arpa/inet.h>providesinet_ntoafunction which we're using to convert an address to human-readable address.<unistd.h>providesclose()function.
- We redefine
Also we're defining startup, cleanup and error code function which will call WSAStartup, WSACleanup in Windows and will be no-op in Unix. And will call WSAGetLastError in Windows and return errno in Unix.
static inline int init_socket() {
#ifdef WIN32
WSADATA wsa;
return WSAStartup(MAKEWORD(2, 0), &wsa);
#else
return 0;
#endif
}
static inline int cleanup_socket() {
#ifdef WIN32
WSADATA wsa;
return WSAStartup(MAKEWORD(2, 0), &wsa);
#else
return 0;
#endif
}
static inline int get_last_socket_error() {
#ifdef WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
With code isolated and some redefined to fit the target platform, we can just replace any occurence of WSAStartup to init_socket() WSACleanup to cleanup_socket() and WSAGetLastError to get_last_socket_error(). And the include part will be like so:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "config.h"
#include "platform.h" // include this
// ... more code below
And also, you may see there's new other header config.h. I move every configuration to this file like MTU and maximum buffer size.
#ifndef OTLS_CONFIG_H
#define OTLS_CONFIG_H 1
#define MTU 1500
#define BUF_MAX_SIZE (8 * 2014)
#endif /* OTLS_CONFIG_H */
With this sorted out, all we need to do is just revise CMakeLists.txt a little bit so it only link to ws2_32.dll on Windows and not doing anything else on Linux or macOS.
if (WIN32)
target_link_libraries(binfetch ws2_32)
endif (WIN32)
Rebuild and Run
The step on macOS and linux is the same. Prepare a directory for building and then use these commands.
cd build
cmake ..
make
./binfetch
The command is short because I believe you're building the software natively. Because the code becomes complex. I've prepared the source repo for you to try.
Here's the result on macOS







