环境准备
更新源
# 更新系统源
sudo apt update && sudo apt upgrade -y
# 安装tree
sudo apt install tree安装clang
# clang
sudo apt update
sudo apt install -y clang lld安装ninja
# ninja
sudo apt install -y ninja-build安装musl
# musl
sudo apt install -y musl musl-dev musl-tools静态编译
ubuntu下使用gcc、clang分别静态和动态编译,并分别结合musl库进行静态与动态编译。如下
helloworld.c
// cat helloworld.c
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
CMakeList.txt
cmake_minimum_required(VERSION 3.10)
project(HelloWorldComparison LANGUAGES C)
# Find compilers
find_program(GCC gcc)
find_program(CLANG clang)
find_program(MUSL_GCC musl-gcc)
# Check for musl headers and libraries (for clang+musl support)
if(EXISTS "/usr/include/x86_64-linux-musl" AND EXISTS "/usr/lib/x86_64-linux-musl")
set(MUSL_CLANG_AVAILABLE TRUE)
set(MUSL_INCLUDE_DIR "/usr/include/x86_64-linux-musl")
set(MUSL_LIB_DIR "/usr/lib/x86_64-linux-musl")
else()
set(MUSL_CLANG_AVAILABLE FALSE)
endif()
message(STATUS "=== Compiler Detection ===")
message(STATUS "GCC: ${GCC}")
message(STATUS "Clang: ${CLANG}")
message(STATUS "musl-gcc: ${MUSL_GCC}")
message(STATUS "Clang+musl support: ${MUSL_CLANG_AVAILABLE}")
message(STATUS "========================")
# ============================================================================
# GCC builds (using default compiler which is typically gcc)
# ============================================================================
# 1. GCC + glibc dynamic
add_executable(hello_gcc_glibc_dynamic helloworld.c)
# 2. GCC + glibc static
add_executable(hello_gcc_glibc_static helloworld.c)
target_link_libraries(hello_gcc_glibc_static PRIVATE -static)
# ============================================================================
# Clang builds
# ============================================================================
if(CLANG)
# 3. Clang + glibc dynamic
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/hello_clang_glibc_dynamic
COMMAND ${CLANG} -o ${CMAKE_BINARY_DIR}/hello_clang_glibc_dynamic ${CMAKE_SOURCE_DIR}/helloworld.c
DEPENDS ${CMAKE_SOURCE_DIR}/helloworld.c
COMMENT "Building clang+glibc dynamic executable"
)
add_custom_target(build_clang_glibc_dynamic ALL DEPENDS ${CMAKE_BINARY_DIR}/hello_clang_glibc_dynamic)
# 4. Clang + glibc static
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/hello_clang_glibc_static
COMMAND ${CLANG} -static -o ${CMAKE_BINARY_DIR}/hello_clang_glibc_static ${CMAKE_SOURCE_DIR}/helloworld.c
DEPENDS ${CMAKE_SOURCE_DIR}/helloworld.c
COMMENT "Building clang+glibc static executable"
)
add_custom_target(build_clang_glibc_static ALL DEPENDS ${CMAKE_BINARY_DIR}/hello_clang_glibc_static)
else()
message(WARNING "clang not found. Skipping clang builds.")
endif()
# ============================================================================
# musl-gcc builds
# ============================================================================
if(MUSL_GCC)
# 5. musl-gcc + musl dynamic (musl prefers static, but we can try dynamic)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/hello_muslgcc_musl_dynamic
COMMAND ${MUSL_GCC} -o ${CMAKE_BINARY_DIR}/hello_muslgcc_musl_dynamic ${CMAKE_SOURCE_DIR}/helloworld.c
DEPENDS ${CMAKE_SOURCE_DIR}/helloworld.c
COMMENT "Building musl-gcc+musl dynamic executable"
)
add_custom_target(build_muslgcc_musl_dynamic ALL DEPENDS ${CMAKE_BINARY_DIR}/hello_muslgcc_musl_dynamic)
# 6. musl-gcc + musl static
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/hello_muslgcc_musl_static
COMMAND ${MUSL_GCC} -static -o ${CMAKE_BINARY_DIR}/hello_muslgcc_musl_static ${CMAKE_SOURCE_DIR}/helloworld.c
DEPENDS ${CMAKE_SOURCE_DIR}/helloworld.c
COMMENT "Building musl-gcc+musl static executable"
)
add_custom_target(build_muslgcc_musl_static ALL DEPENDS ${CMAKE_BINARY_DIR}/hello_muslgcc_musl_static)
else()
message(WARNING "musl-gcc not found. Skipping musl-gcc builds.")
endif()
# ============================================================================
# Clang + musl builds
# ============================================================================
if(CLANG AND MUSL_CLANG_AVAILABLE)
# 7. Clang + musl dynamic
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/hello_clang_musl_dynamic
COMMAND ${CLANG} -target x86_64-linux-musl -nostdinc -isystem ${MUSL_INCLUDE_DIR} -L${MUSL_LIB_DIR} ${CMAKE_SOURCE_DIR}/helloworld.c -lc -o ${CMAKE_BINARY_DIR}/hello_clang_musl_dynamic
DEPENDS ${CMAKE_SOURCE_DIR}/helloworld.c
COMMENT "Building clang+musl dynamic executable"
)
add_custom_target(build_clang_musl_dynamic ALL DEPENDS ${CMAKE_BINARY_DIR}/hello_clang_musl_dynamic)
# 8. Clang + musl static
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/hello_clang_musl_static
COMMAND ${CLANG} -target x86_64-linux-musl -static -nostdinc -isystem ${MUSL_INCLUDE_DIR} -L${MUSL_LIB_DIR} ${CMAKE_SOURCE_DIR}/helloworld.c -lc -o ${CMAKE_BINARY_DIR}/hello_clang_musl_static
DEPENDS ${CMAKE_SOURCE_DIR}/helloworld.c
COMMENT "Building clang+musl static executable"
)
add_custom_target(build_clang_musl_static ALL DEPENDS ${CMAKE_BINARY_DIR}/hello_clang_musl_static)
elseif(CLANG AND NOT MUSL_CLANG_AVAILABLE)
message(WARNING "musl headers/libraries not found. Skipping clang+musl builds.")
endif()
构建
# 使用make构建
cmake -B build
cmake --build build
# 使用ninja构建
cmake -B build
cmake --build build效果
$ tree -L 2
.
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── hello_clang_dynamic
│ ├── hello_clang_static
│ ├── hello_glibc_dynamic
│ ├── hello_glibc_static
│ ├── hello_musl_static
│ └── Makefile
├── CMakeLists.txt
├── helloworld.c
├── ninja
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── hello_clang_glibc_dynamic
│ ├── hello_clang_glibc_static
│ ├── hello_clang_musl_dynamic
│ ├── hello_clang_musl_static
│ ├── hello_gcc_glibc_dynamic
│ ├── hello_gcc_glibc_static
│ ├── hello_muslgcc_musl_dynamic
│ ├── hello_muslgcc_musl_static
│ └── Makefile
└── requiremetn.md
5 directories, 22 files单文件发行
将所需要的配置文件、Web文件都打包进入目标文件。可以使用多种方式objcopy是不错的一种。还可以结合tar或者自定义的hash方式预处理形成目标文件.o,链接进入发行包。
加入配置文件
目录结构
$ tree
.
├── CMakeLists.txt
├── data
│ ├── a.html
│ ├── a.ini
│ └── a.json
├── main.cmain.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* Embedded data files - these will be linked into the executable
* The data is embedded using objcopy or linker scripts
*/
// Declare external symbols for embedded data
// These symbols are created by objcopy when embedding binary files
// Format: _binary_<filename>_<start|end|size>
extern const char _binary_a_html_start[];
extern const char _binary_a_html_end[];
extern const char _binary_a_json_start[];
extern const char _binary_a_json_end[];
extern const char _binary_a_ini_start[];
extern const char _binary_a_ini_end[];
// Function to print usage
void print_usage(const char *program_name) {
printf("Usage: %s [option]\n", program_name);
printf("Options:\n");
printf(" --html Output embedded HTML file\n");
printf(" --json Output embedded JSON file\n");
printf(" --ini Output embedded INI file\n");
printf(" --list List all embedded files\n");
printf(" --help Show this help message\n");
}
// Function to output embedded file content
void output_embedded_file(const char *name, const char *start, const char *end) {
size_t size = end - start;
printf("=== %s (size: %zu bytes) ===\n", name, size);
fwrite(start, 1, size, stdout);
printf("\n");
}
// Function to list embedded files
void list_embedded_files() {
printf("Embedded files:\n");
printf(" 1. a.html (%zu bytes)\n", _binary_a_html_end - _binary_a_html_start);
printf(" 2. a.json (%zu bytes)\n", _binary_a_json_end - _binary_a_json_start);
printf(" 3. a.ini (%zu bytes)\n", _binary_a_ini_end - _binary_a_ini_start);
}
int main(int argc, char *argv[]) {
if (argc != 2) {
print_usage(argv[0]);
return 1;
}
if (strcmp(argv[1], "--html") == 0) {
output_embedded_file("a.html", _binary_a_html_start, _binary_a_html_end);
} else if (strcmp(argv[1], "--json") == 0) {
output_embedded_file("a.json", _binary_a_json_start, _binary_a_json_end);
} else if (strcmp(argv[1], "--ini") == 0) {
output_embedded_file("a.ini", _binary_a_ini_start, _binary_a_ini_end);
} else if (strcmp(argv[1], "--list") == 0) {
list_embedded_files();
} else if (strcmp(argv[1], "--help") == 0) {
print_usage(argv[0]);
} else {
fprintf(stderr, "Unknown option: %s\n", argv[1]);
print_usage(argv[0]);
return 1;
}
return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(DataInExe LANGUAGES C)
# Find compilers
find_program(MUSL_GCC musl-gcc)
find_program(CLANG clang)
# Check for musl support
if(EXISTS "/usr/include/x86_64-linux-musl" AND EXISTS "/usr/lib/x86_64-linux-musl")
set(MUSL_CLANG_AVAILABLE TRUE)
set(MUSL_INCLUDE_DIR "/usr/include/x86_64-linux-musl")
set(MUSL_LIB_DIR "/usr/lib/x86_64-linux-musl")
endif()
message(STATUS "musl-gcc: ${MUSL_GCC}")
message(STATUS "clang: ${CLANG}")
message(STATUS "clang+musl: ${MUSL_CLANG_AVAILABLE}")
# Define data directory
set(DATA_DIR ${CMAKE_SOURCE_DIR}/data)
# Embed data files using objcopy
function(embed_data_file INPUT_FILE OUTPUT_OBJ SYMBOL_PREFIX)
get_filename_component(FILENAME ${INPUT_FILE} NAME)
set(SIMPLE_INPUT ${CMAKE_BINARY_DIR}/${FILENAME})
add_custom_command(
OUTPUT ${OUTPUT_OBJ}
COMMAND ${CMAKE_COMMAND} -E copy ${INPUT_FILE} ${SIMPLE_INPUT}
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR}
objcopy --input binary --output elf64-x86-64
--rename-section .data=.rodata,alloc,load,readonly,data,contents
--redefine-sym _binary_${FILENAME}_start=${SYMBOL_PREFIX}_start
--redefine-sym _binary_${FILENAME}_end=${SYMBOL_PREFIX}_end
--redefine-sym _binary_${FILENAME}_size=${SYMBOL_PREFIX}_size
--add-section .note.GNU-stack=/dev/null
--set-section-flags .note.GNU-stack=readonly
${FILENAME} ${OUTPUT_OBJ}
DEPENDS ${INPUT_FILE}
COMMENT "Embedding ${FILENAME}"
)
endfunction()
embed_data_file(${DATA_DIR}/a.html ${CMAKE_BINARY_DIR}/a_html.o data_a_html)
embed_data_file(${DATA_DIR}/a.json ${CMAKE_BINARY_DIR}/a_json.o data_a_json)
embed_data_file(${DATA_DIR}/a.ini ${CMAKE_BINARY_DIR}/a_ini.o data_a_ini)
# Build with musl-gcc
if(MUSL_GCC)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/main_muslgcc.o
COMMAND ${MUSL_GCC} -c ${CMAKE_SOURCE_DIR}/main.c -o ${CMAKE_BINARY_DIR}/main_muslgcc.o
DEPENDS ${CMAKE_SOURCE_DIR}/main.c
COMMENT "Compiling with musl-gcc"
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/data_in_exe_muslgcc_static
COMMAND ${MUSL_GCC} -static
${CMAKE_BINARY_DIR}/main_muslgcc.o
${CMAKE_BINARY_DIR}/a_html.o
${CMAKE_BINARY_DIR}/a_json.o
${CMAKE_BINARY_DIR}/a_ini.o
-o ${CMAKE_BINARY_DIR}/data_in_exe_muslgcc_static
DEPENDS ${CMAKE_BINARY_DIR}/main_muslgcc.o
${CMAKE_BINARY_DIR}/a_html.o
${CMAKE_BINARY_DIR}/a_json.o
${CMAKE_BINARY_DIR}/a_ini.o
COMMENT "Linking musl-gcc static"
)
add_custom_target(build_muslgcc_static ALL
DEPENDS ${CMAKE_BINARY_DIR}/data_in_exe_muslgcc_static
)
endif()
# Build with clang+musl
if(CLANG AND MUSL_CLANG_AVAILABLE)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/main_clang_musl.o
COMMAND ${CLANG} -target x86_64-linux-musl -nostdinc -isystem ${MUSL_INCLUDE_DIR}
-c ${CMAKE_SOURCE_DIR}/main.c -o ${CMAKE_BINARY_DIR}/main_clang_musl.o
DEPENDS ${CMAKE_SOURCE_DIR}/main.c
COMMENT "Compiling with clang+musl"
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/data_in_exe_clang_musl_static
COMMAND ${CLANG} -target x86_64-linux-musl -static -nostdinc -isystem ${MUSL_INCLUDE_DIR}
-L${MUSL_LIB_DIR}
${CMAKE_BINARY_DIR}/main_clang_musl.o
${CMAKE_BINARY_DIR}/a_html.o
${CMAKE_BINARY_DIR}/a_json.o
${CMAKE_BINARY_DIR}/a_ini.o
-lc -o ${CMAKE_BINARY_DIR}/data_in_exe_clang_musl_static
DEPENDS ${CMAKE_BINARY_DIR}/main_clang_musl.o
${CMAKE_BINARY_DIR}/a_html.o
${CMAKE_BINARY_DIR}/a_json.o
${CMAKE_BINARY_DIR}/a_ini.o
COMMENT "Linking clang+musl static"
)
add_custom_target(build_clang_musl_static ALL
DEPENDS ${CMAKE_BINARY_DIR}/data_in_exe_clang_musl_static
)
endif()
message(STATUS "")
message(STATUS "Build targets:")
if(MUSL_GCC)
message(STATUS " - data_in_exe_muslgcc_static")
endif()
if(CLANG AND MUSL_CLANG_AVAILABLE)
message(STATUS " - data_in_exe_clang_musl_static")
endif()
message(STATUS "")
加入tar组装的web目录
使用tar将web目录打包为一个整块,然后用objcopy将其构建成.o,链接进入发行包。
目录结构
$ tree
.
├── CMakeLists.txt
├── htdocs
│ ├── css
│ │ └── index.css
│ ├── img
│ │ └── image.png
│ ├── index.html
│ └── js
│ ├── index.js
│ └── test.js
├── main.cmain.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/*
* Embedded tar archive - this will be linked into the executable
* The tar file is created from the htdocs directory and embedded using objcopy
*/
// Declare external symbols for embedded tar data
extern const char _binary_htdocs_tar_start[];
extern const char _binary_htdocs_tar_end[];
// TAR header structure (POSIX ustar format)
typedef struct {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char checksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char padding[12];
} tar_header_t;
// Function to convert octal string to integer
static int octal_to_int(const char *str, int len) {
int result = 0;
for (int i = 0; i < len && str[i] != '\0' && str[i] != ' '; i++) {
if (str[i] >= '0' && str[i] <= '7') {
result = result * 8 + (str[i] - '0');
}
}
return result;
}
// Function to find a file in the tar archive
static const char* find_file_in_tar(const char *tar_data, size_t tar_size,
const char *filename, size_t *file_size) {
const char *ptr = tar_data;
const char *end = tar_data + tar_size;
while (ptr < end) {
tar_header_t *header = (tar_header_t *)ptr;
// Check for end of archive (two consecutive zero blocks)
if (header->name[0] == '\0') {
break;
}
// Calculate file size from header
size_t size = octal_to_int(header->size, sizeof(header->size));
// Get filename from header
char entry_name[256] = {0};
if (header->prefix[0] != '\0') {
strncpy(entry_name, header->prefix, sizeof(header->prefix));
strncat(entry_name, "/", sizeof(entry_name) - strlen(entry_name) - 1);
}
strncat(entry_name, header->name, sizeof(entry_name) - strlen(entry_name) - 1);
// Remove trailing slash for directories
size_t name_len = strlen(entry_name);
if (name_len > 0 && entry_name[name_len - 1] == '/') {
entry_name[name_len - 1] = '\0';
}
// Check if this is the file we're looking for
if (strcmp(entry_name, filename) == 0 && header->typeflag == '0') {
// Regular file found
*file_size = size;
return ptr + 512; // Data starts after the 512-byte header
}
// Move to next entry (header + data rounded up to 512 bytes)
size_t data_size = (size + 511) & ~511; // Round up to nearest 512
ptr += 512 + data_size;
}
return NULL; // File not found
}
// Function to list all files in the tar archive
static void list_tar_files(const char *tar_data, size_t tar_size) {
const char *ptr = tar_data;
const char *end = tar_data + tar_size;
int file_count = 0;
printf("Files in embedded htdocs:\n");
while (ptr < end) {
tar_header_t *header = (tar_header_t *)ptr;
// Check for end of archive
if (header->name[0] == '\0') {
break;
}
// Get filename from header
char entry_name[256] = {0};
if (header->prefix[0] != '\0') {
strncpy(entry_name, header->prefix, sizeof(header->prefix));
strncat(entry_name, "/", sizeof(entry_name) - strlen(entry_name) - 1);
}
strncat(entry_name, header->name, sizeof(entry_name) - strlen(entry_name) - 1);
// Calculate file size
size_t size = octal_to_int(header->size, sizeof(header->size));
// Determine file type
const char *type_str = "unknown";
switch (header->typeflag) {
case '0': type_str = "file"; break;
case '5': type_str = "dir"; break;
default: type_str = "other"; break;
}
printf(" [%s] %s (%zu bytes)\n", type_str, entry_name, size);
file_count++;
// Move to next entry
size_t data_size = (size + 511) & ~511;
ptr += 512 + data_size;
}
printf("\nTotal: %d entries\n", file_count);
}
// Function to print usage
static void print_usage(const char *program_name) {
printf("Usage: %s [option] [filepath]\n", program_name);
printf("\nOptions:\n");
printf(" --list List all files in embedded htdocs\n");
printf(" --get <filepath> Extract and display file content\n");
printf(" --hmi Start interactive HMI mode\n");
printf(" --help Show this help message\n");
printf("\nExamples:\n");
printf(" %s --list\n", program_name);
printf(" %s --get index.html\n", program_name);
printf(" %s --get css/index.css\n", program_name);
printf(" %s --get js/index.js\n", program_name);
printf(" %s --hmi\n", program_name);
}
// Function to check if a file is binary (contains non-text characters)
static int is_binary_file(const char *data, size_t size) {
// Check first 512 bytes or entire file if smaller
size_t check_size = size < 512 ? size : 512;
for (size_t i = 0; i < check_size; i++) {
unsigned char c = (unsigned char)data[i];
// Common text characters: printable ASCII, tab, newline, carriage return
if (c < 32 && c != '\t' && c != '\n' && c != '\r') {
return 1; // Binary file
}
}
return 0; // Text file
}
// Function to display file content in hex dump format (similar to xxd)
static void hex_dump(const char *data, size_t size) {
size_t offset = 0;
while (offset < size) {
// Print offset
printf("%08lx: ", (unsigned long)offset);
// Print hex bytes
for (int i = 0; i < 16; i++) {
if (offset + i < size) {
printf("%02x ", (unsigned char)data[offset + i]);
} else {
printf(" ");
}
if (i == 7) printf(" "); // Extra space in the middle
}
// Print ASCII representation
printf(" |");
for (int i = 0; i < 16; i++) {
if (offset + i < size) {
unsigned char c = (unsigned char)data[offset + i];
if (c >= 32 && c <= 126) {
printf("%c", c);
} else {
printf(".");
}
}
}
printf("|\n");
offset += 16;
}
printf("Total: %zu bytes\n", size);
}
// Interactive HMI mode
static void interactive_mode(const char *tar_data, size_t tar_size) {
char input[1024];
char filepath[1100]; // Buffer for filepath with htdocs/ prefix
printf("=== Htdocs Interactive Mode ===\n");
printf("Enter file path to view content (e.g., index.html, css/style.css)\n");
printf("Type '!exit' to quit\n");
printf("Type '!list' to see available files\n");
printf("================================\n\n");
while (1) {
printf("htdocs> ");
fflush(stdout);
// Read input
if (fgets(input, sizeof(input), stdin) == NULL) {
break;
}
// Remove trailing newline
size_t len = strlen(input);
if (len > 0 && input[len - 1] == '\n') {
input[len - 1] = '\0';
len--;
}
// Skip empty input
if (len == 0) {
continue;
}
// Check for exit command
if (strcmp(input, "!exit") == 0) {
printf("Exiting interactive mode. Goodbye!\n");
break;
}
// Check for list command
if (strcmp(input, "!list") == 0) {
list_tar_files(tar_data, tar_size);
printf("\n");
continue;
}
// Add htdocs/ prefix if not already present
if (strncmp(input, "htdocs/", 7) != 0) {
snprintf(filepath, sizeof(filepath), "htdocs/%s", input);
} else {
strncpy(filepath, input, sizeof(filepath) - 1);
filepath[sizeof(filepath) - 1] = '\0';
}
// Try to find and display the file
size_t file_size = 0;
const char *file_data = find_file_in_tar(tar_data, tar_size, filepath, &file_size);
if (file_data == NULL) {
fprintf(stderr, "Error: File '%s' not found in embedded htdocs\n", input);
printf("\n");
continue;
}
// Display file information
printf("\n--- File: %s (%zu bytes) ---\n", input, file_size);
// Check if binary or text
if (is_binary_file(file_data, file_size)) {
printf("Binary file detected. Displaying hex dump:\n\n");
hex_dump(file_data, file_size);
} else {
printf("Text file content:\n\n");
fwrite(file_data, 1, file_size, stdout);
// Ensure output ends with newline
if (file_size > 0 && file_data[file_size - 1] != '\n') {
printf("\n");
}
}
printf("\n--- End of file ---\n\n");
}
}
int main(int argc, char *argv[]) {
const char *tar_data = _binary_htdocs_tar_start;
size_t tar_size = _binary_htdocs_tar_end - _binary_htdocs_tar_start;
if (argc < 2) {
print_usage(argv[0]);
return 1;
}
if (strcmp(argv[1], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
if (strcmp(argv[1], "--list") == 0) {
list_tar_files(tar_data, tar_size);
return 0;
}
if (strcmp(argv[1], "--hmi") == 0) {
interactive_mode(tar_data, tar_size);
return 0;
}
if (strcmp(argv[1], "--get") == 0) {
if (argc < 3) {
fprintf(stderr, "Error: --get requires a filepath argument\n");
print_usage(argv[0]);
return 1;
}
const char *input_path = argv[2];
char filepath[1100];
// Add htdocs/ prefix if not already present
if (strncmp(input_path, "htdocs/", 7) != 0) {
snprintf(filepath, sizeof(filepath), "htdocs/%s", input_path);
} else {
strncpy(filepath, input_path, sizeof(filepath) - 1);
filepath[sizeof(filepath) - 1] = '\0';
}
size_t file_size = 0;
const char *file_data = find_file_in_tar(tar_data, tar_size, filepath, &file_size);
if (file_data == NULL) {
fprintf(stderr, "Error: File '%s' not found in embedded htdocs\n", input_path);
return 1;
}
// Output file content
fwrite(file_data, 1, file_size, stdout);
return 0;
}
fprintf(stderr, "Error: Unknown option '%s'\n", argv[1]);
print_usage(argv[0]);
return 1;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(HtdocsInExe LANGUAGES C)
# Find compilers
find_program(CLANG clang)
# Check for musl support
if(EXISTS "/usr/include/x86_64-linux-musl" AND EXISTS "/usr/lib/x86_64-linux-musl")
set(MUSL_CLANG_AVAILABLE TRUE)
set(MUSL_INCLUDE_DIR "/usr/include/x86_64-linux-musl")
set(MUSL_LIB_DIR "/usr/lib/x86_64-linux-musl")
endif()
message(STATUS "clang: ${CLANG}")
message(STATUS "clang+musl: ${MUSL_CLANG_AVAILABLE}")
# Define htdocs directory
set(HTDOCS_DIR ${CMAKE_SOURCE_DIR}/htdocs)
# Automatically find all files in htdocs directory
file(GLOB_RECURSE HTDOCS_FILES RELATIVE ${HTDOCS_DIR} "${HTDOCS_DIR}/*")
# Create tar archive from htdocs directory
set(TAR_FILE ${CMAKE_BINARY_DIR}/htdocs.tar)
# Convert relative paths to absolute paths for dependencies
set(HTDOCS_FILES_ABS "")
foreach(FILE ${HTDOCS_FILES})
list(APPEND HTDOCS_FILES_ABS "${HTDOCS_DIR}/${FILE}")
endforeach()
add_custom_command(
OUTPUT ${TAR_FILE}
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR}
tar cf ${TAR_FILE} htdocs
DEPENDS ${HTDOCS_FILES_ABS}
COMMENT "Creating tar archive of htdocs directory (${HTDOCS_FILES} files)"
)
# Embed tar file using objcopy
set(TAR_OBJ ${CMAKE_BINARY_DIR}/htdocs_tar.o)
add_custom_command(
OUTPUT ${TAR_OBJ}
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR}
objcopy --input binary --output elf64-x86-64
--rename-section .data=.rodata,alloc,load,readonly,data,contents
--redefine-sym _binary_htdocs_tar_start=_binary_htdocs_tar_start
--redefine-sym _binary_htdocs_tar_end=_binary_htdocs_tar_end
--redefine-sym _binary_htdocs_tar_size=_binary_htdocs_tar_size
--add-section .note.GNU-stack=/dev/null
--set-section-flags .note.GNU-stack=readonly
htdocs.tar ${TAR_OBJ}
DEPENDS ${TAR_FILE}
COMMENT "Embedding htdocs.tar into object file"
)
# Build with clang+musl
if(CLANG AND MUSL_CLANG_AVAILABLE)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/main_clang_musl.o
COMMAND ${CLANG} -target x86_64-linux-musl -nostdinc -isystem ${MUSL_INCLUDE_DIR}
-c ${CMAKE_SOURCE_DIR}/main.c -o ${CMAKE_BINARY_DIR}/main_clang_musl.o
DEPENDS ${CMAKE_SOURCE_DIR}/main.c
COMMENT "Compiling with clang+musl"
)
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/htdocs_in_exe_clang_musl_static
COMMAND ${CLANG} -target x86_64-linux-musl -static -nostdinc -isystem ${MUSL_INCLUDE_DIR}
-L${MUSL_LIB_DIR}
${CMAKE_BINARY_DIR}/main_clang_musl.o
${TAR_OBJ}
-lc -o ${CMAKE_BINARY_DIR}/htdocs_in_exe_clang_musl_static
DEPENDS ${CMAKE_BINARY_DIR}/main_clang_musl.o
${TAR_OBJ}
COMMENT "Linking clang+musl static executable"
)
add_custom_target(build_clang_musl_static ALL
DEPENDS ${CMAKE_BINARY_DIR}/htdocs_in_exe_clang_musl_static
)
endif()
message(STATUS "")
message(STATUS "Build targets:")
if(CLANG AND MUSL_CLANG_AVAILABLE)
message(STATUS " - htdocs_in_exe_clang_musl_static")
endif()
message(STATUS "")