ocran 1.3.18 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.txt +306 -292
- data/LICENSE.txt +22 -22
- data/README.md +549 -533
- data/exe/ocran +5 -5
- data/ext/extconf.rb +15 -0
- data/lib/ocran/build_constants.rb +16 -16
- data/lib/ocran/build_facade.rb +17 -17
- data/lib/ocran/build_helper.rb +110 -105
- data/lib/ocran/command_output.rb +22 -22
- data/lib/ocran/dir_builder.rb +162 -0
- data/lib/ocran/direction.rb +623 -458
- data/lib/ocran/file_path_set.rb +69 -69
- data/lib/ocran/gem_spec_queryable.rb +172 -172
- data/lib/ocran/host_config_helper.rb +57 -44
- data/lib/ocran/inno_setup_script_builder.rb +111 -111
- data/lib/ocran/launcher_batch_builder.rb +85 -85
- data/lib/ocran/library_detector.rb +61 -61
- data/lib/ocran/library_detector_posix.rb +55 -0
- data/lib/ocran/option.rb +323 -273
- data/lib/ocran/refine_pathname.rb +104 -104
- data/lib/ocran/runner.rb +115 -105
- data/lib/ocran/runtime_environment.rb +46 -46
- data/lib/ocran/stub_builder.rb +298 -264
- data/lib/ocran/version.rb +5 -5
- data/lib/ocran/windows_command_escaping.rb +15 -15
- data/lib/ocran.rb +7 -7
- data/share/ocran/lzma.exe +0 -0
- data/src/Makefile +75 -0
- data/src/edicon.c +161 -0
- data/src/error.c +100 -0
- data/src/error.h +66 -0
- data/src/inst_dir.c +334 -0
- data/src/inst_dir.h +157 -0
- data/src/lzma/7zTypes.h +529 -0
- data/src/lzma/Compiler.h +43 -0
- data/src/lzma/LzmaDec.c +1363 -0
- data/src/lzma/LzmaDec.h +236 -0
- data/src/lzma/Precomp.h +10 -0
- data/src/script_info.c +246 -0
- data/src/script_info.h +7 -0
- data/src/stub.c +133 -0
- data/src/stub.manifest +29 -0
- data/src/stub.rc +3 -0
- data/src/system_utils.c +1002 -0
- data/src/system_utils.h +209 -0
- data/src/system_utils_posix.c +500 -0
- data/src/unpack.c +574 -0
- data/src/unpack.h +85 -0
- data/src/vit-ruby.ico +0 -0
- metadata +52 -16
- data/share/ocran/edicon.exe +0 -0
- data/share/ocran/stub.exe +0 -0
- data/share/ocran/stubw.exe +0 -0
data/src/system_utils.h
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#ifdef _WIN32
|
|
2
|
+
#include <windows.h>
|
|
3
|
+
#endif
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stddef.h>
|
|
6
|
+
|
|
7
|
+
#ifdef _WIN32
|
|
8
|
+
#define PATH_SEPARATOR '\\'
|
|
9
|
+
#else
|
|
10
|
+
#define PATH_SEPARATOR '/'
|
|
11
|
+
#endif
|
|
12
|
+
|
|
13
|
+
static inline bool is_path_separator(char c) {
|
|
14
|
+
#ifdef _WIN32
|
|
15
|
+
return c == '\\' || c == '/';
|
|
16
|
+
#else
|
|
17
|
+
return c == '/';
|
|
18
|
+
#endif
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @brief Check if a path is a “clean” relative path.
|
|
23
|
+
*
|
|
24
|
+
* A clean relative path satisfies all of the following:
|
|
25
|
+
* - Non-NULL, non-empty (`path != NULL && *path != '\0'`)
|
|
26
|
+
* - Does not start with a path separator (`'/'` or `'\'`)
|
|
27
|
+
* - On Windows, does not use a drive-letter specifier (e.g. `"C:\"`)
|
|
28
|
+
* - Contains no empty segments (no `"//"` or `"\\"`)
|
|
29
|
+
* - Contains no `"."` or `".."` segments
|
|
30
|
+
*
|
|
31
|
+
* @param path A null-terminated string representing the path to validate.
|
|
32
|
+
* @return true if the input meets all clean-relative-path criteria;
|
|
33
|
+
* false otherwise.
|
|
34
|
+
*/
|
|
35
|
+
bool IsCleanRelativePath(const char *path);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* JoinPath - Combines two file path components into a single path.
|
|
39
|
+
*
|
|
40
|
+
* @param p1 The first path component.
|
|
41
|
+
* @param p2 The second path component.
|
|
42
|
+
* @return A pointer to a newly allocated string that represents the combined file path.
|
|
43
|
+
* Returns NULL if either input is NULL, empty, or if memory allocation fails.
|
|
44
|
+
*/
|
|
45
|
+
char *JoinPath(const char *p1, const char *p2);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @brief Returns a newly allocated string containing the parent
|
|
49
|
+
* directory for a given path.
|
|
50
|
+
* @param path Input path (must be non-NULL).
|
|
51
|
+
* @return
|
|
52
|
+
* - NULL if path is NULL or on allocation failure.
|
|
53
|
+
* - "" if path is empty or has no parent segment.
|
|
54
|
+
* - otherwise malloc’d NUL-terminated parent path (caller must free).
|
|
55
|
+
*/
|
|
56
|
+
char *GetParentPath(const char *path);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* CreateDirectoriesRecursively - Creates a directory and all its parent directories if they do not exist.
|
|
60
|
+
*
|
|
61
|
+
* @param dir The path of the directory to create.
|
|
62
|
+
* @return true if the directory was successfully created or already exists.
|
|
63
|
+
* false if the directory could not be created due to an error.
|
|
64
|
+
*/
|
|
65
|
+
bool CreateDirectoriesRecursively(const char *dir);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* DeleteRecursively - Deletes a directory and all its contents recursively.
|
|
69
|
+
*
|
|
70
|
+
* @param path The path of the directory to delete.
|
|
71
|
+
* @return true if the directory and its contents were successfully deleted.
|
|
72
|
+
* false if the directory could not be fully deleted due to an error.
|
|
73
|
+
*/
|
|
74
|
+
bool DeleteRecursively(const char *path);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @brief Windows-side replacement for POSIX mkdtemp().
|
|
78
|
+
*
|
|
79
|
+
* The string \t tmpl must end with at least six ’X’ characters.
|
|
80
|
+
* Those X’s are replaced with random portable-filename characters and
|
|
81
|
+
* the function tries to create the directory.
|
|
82
|
+
*
|
|
83
|
+
* @param tmpl Writable, NUL-terminated buffer ending in “XXXXXX”.
|
|
84
|
+
* @return tmpl on success (now holding the new path), or NULL on error
|
|
85
|
+
* with errno set.
|
|
86
|
+
*/
|
|
87
|
+
char *CreateUniqueDirectory(char *tmpl);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* GetImagePath - Retrieves the full path of the executable file of the current process.
|
|
91
|
+
*
|
|
92
|
+
* @return A pointer to a newly allocated string that represents the full path of the executable.
|
|
93
|
+
* Returns NULL if the path could not be retrieved or if memory allocation fails.
|
|
94
|
+
*/
|
|
95
|
+
char *GetImagePath(void);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* GetTempDirectoryPath - Retrieves the path of the temporary directory for the current user.
|
|
99
|
+
*
|
|
100
|
+
* @return A pointer to a newly allocated string that represents the path of the temporary directory.
|
|
101
|
+
* Returns NULL if the path could not be retrieved or if memory allocation fails.
|
|
102
|
+
*/
|
|
103
|
+
char *GetTempDirectoryPath(void);
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @brief Writes the contents of a buffer to the specified file path.
|
|
107
|
+
* Creates any missing parent directories and overwrites existing files.
|
|
108
|
+
*
|
|
109
|
+
* @param path Output file path (absolute or relative).
|
|
110
|
+
* @param buffer Pointer to the data buffer to write.
|
|
111
|
+
* @param buffer_size Size of the data buffer in bytes.
|
|
112
|
+
* @return true if the write succeeded, false otherwise.
|
|
113
|
+
*/
|
|
114
|
+
bool ExportFile(const char *path, const void *buffer, size_t buffer_size);
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @brief Opaque handle to a memory-mapped file region.
|
|
118
|
+
*
|
|
119
|
+
* The contents of this structure are private; users must
|
|
120
|
+
* obtain and destroy instances via the API functions below.
|
|
121
|
+
*/
|
|
122
|
+
typedef struct MemoryMap MemoryMap;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @brief Creates a memory map for the entire contents of a file.
|
|
126
|
+
*
|
|
127
|
+
* Opens @p path in read-only mode and maps its full length
|
|
128
|
+
* into memory. The returned pointer must later be passed to
|
|
129
|
+
* DestroyMemoryMap() to unmap and free resources.
|
|
130
|
+
*
|
|
131
|
+
* @param path Path to an existing file to map; must not be NULL.
|
|
132
|
+
* @return Pointer to a new MemoryMap on success, or NULL on failure.
|
|
133
|
+
*/
|
|
134
|
+
MemoryMap *CreateMemoryMap(const char *path);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @brief Unmaps and destroys a MemoryMap object.
|
|
138
|
+
*
|
|
139
|
+
* Releases the mapped view and frees all associated resources.
|
|
140
|
+
* After this call, @p map is no longer valid. Passing NULL
|
|
141
|
+
* will log an error but otherwise do nothing.
|
|
142
|
+
*
|
|
143
|
+
* @param map MemoryMap instance to destroy.
|
|
144
|
+
*/
|
|
145
|
+
void DestroyMemoryMap(MemoryMap *map);
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @brief Returns the base address of the mapped view.
|
|
149
|
+
*
|
|
150
|
+
* @param map A valid MemoryMap returned by CreateMemoryMap.
|
|
151
|
+
* Must not be NULL.
|
|
152
|
+
* @return Base address of the mapping, or NULL if @p map is NULL.
|
|
153
|
+
*/
|
|
154
|
+
void *GetMemoryMapBase(const MemoryMap *map);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @brief Returns the size of the mapped region in bytes.
|
|
158
|
+
*
|
|
159
|
+
* @param map A valid MemoryMap returned by CreateMemoryMap.
|
|
160
|
+
* Must not be NULL.
|
|
161
|
+
* @return Size in bytes of the mapping, or 0 if @p map is NULL.
|
|
162
|
+
* Note: 0 may also be a valid size for an empty file.
|
|
163
|
+
*/
|
|
164
|
+
size_t GetMemoryMapSize(const MemoryMap *map);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @brief Initialize signal and control handling.
|
|
168
|
+
*
|
|
169
|
+
* This function sets up console control and POSIX signal handlers so that
|
|
170
|
+
* the parent process is not prematurely terminated during initialization and
|
|
171
|
+
* cleanup phases. On Windows, a console control handler is registered to ignore
|
|
172
|
+
* control events (e.g., Ctrl+C) in the parent process. On POSIX, relevant
|
|
173
|
+
* signals (SIGINT, SIGTERM, SIGHUP) can be configured to allow cleanup
|
|
174
|
+
* processing before exit.
|
|
175
|
+
*
|
|
176
|
+
* @return
|
|
177
|
+
* - true if initialization succeeded
|
|
178
|
+
* - false if an error occurred (e.g., SetConsoleCtrlHandler failed)
|
|
179
|
+
*/
|
|
180
|
+
bool InitializeSignalHandling(void);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @brief Sets or removes an environment variable.
|
|
184
|
+
*
|
|
185
|
+
* Sets the specified environment variable to the given value. If value is NULL,
|
|
186
|
+
* the variable will be removed from the environment. Returns true on success,
|
|
187
|
+
* false if the operation fails.
|
|
188
|
+
*
|
|
189
|
+
* @param name Name of the environment variable to set or remove.
|
|
190
|
+
* @param value Value to assign to the variable, or NULL to remove it.
|
|
191
|
+
* @return true if the operation succeeded; false otherwise.
|
|
192
|
+
*/
|
|
193
|
+
bool SetEnvVar(const char *name, const char *value);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @brief Launches the specified application with given arguments,
|
|
197
|
+
* waits for it to finish, and retrieves its exit code.
|
|
198
|
+
*
|
|
199
|
+
* @param app_name
|
|
200
|
+
* Path of the executable to run.
|
|
201
|
+
* @param argv
|
|
202
|
+
* NULL-terminated array of argument strings; each element is one argument.
|
|
203
|
+
* @param exit_code
|
|
204
|
+
* Pointer to an int where the child process’s exit code will be stored.
|
|
205
|
+
* @return
|
|
206
|
+
* True if the process was successfully created, waited on, and its exit
|
|
207
|
+
* code retrieved; false otherwise.
|
|
208
|
+
*/
|
|
209
|
+
bool CreateAndWaitForProcess(const char *app_name, char *argv[], int *exit_code);
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
#ifdef __APPLE__
|
|
2
|
+
#include <mach-o/dyld.h>
|
|
3
|
+
#endif
|
|
4
|
+
#include <unistd.h>
|
|
5
|
+
#include <fcntl.h>
|
|
6
|
+
#include <sys/stat.h>
|
|
7
|
+
#include <sys/mman.h>
|
|
8
|
+
#include <sys/wait.h>
|
|
9
|
+
#include <sys/types.h>
|
|
10
|
+
#include <dirent.h>
|
|
11
|
+
#include <signal.h>
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
#include <errno.h>
|
|
15
|
+
#include <stdio.h>
|
|
16
|
+
#include <stdbool.h>
|
|
17
|
+
#include "error.h"
|
|
18
|
+
#include "system_utils.h"
|
|
19
|
+
|
|
20
|
+
/* Opaque handle for memory-mapped files */
|
|
21
|
+
struct MemoryMap {
|
|
22
|
+
int fd;
|
|
23
|
+
void *base;
|
|
24
|
+
size_t size;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/* ===== Cross-platform path utilities (from system_utils.c) ===== */
|
|
28
|
+
|
|
29
|
+
bool IsCleanRelativePath(const char *path)
|
|
30
|
+
{
|
|
31
|
+
if (!path || !*path) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#ifdef _WIN32
|
|
36
|
+
/* Forbid Windows drive specification (e.g. "C:\") */
|
|
37
|
+
if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
|
|
38
|
+
&& path[1] == ':'
|
|
39
|
+
&& is_path_separator(path[2])) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
#endif
|
|
43
|
+
|
|
44
|
+
/* Forbid absolute path (leading '/' or '\') */
|
|
45
|
+
if (is_path_separator(*path)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Validate each path segment */
|
|
50
|
+
const char *p = path;
|
|
51
|
+
while (*p) {
|
|
52
|
+
const char *start = p;
|
|
53
|
+
|
|
54
|
+
/* Advance until next separator or end-of-string */
|
|
55
|
+
while (*p && !is_path_separator(*p)) {
|
|
56
|
+
p++;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
size_t len = p - start;
|
|
60
|
+
|
|
61
|
+
/* Reject empty, "." or ".." segments */
|
|
62
|
+
if (len == 0
|
|
63
|
+
|| (len == 1 && start[0] == '.')
|
|
64
|
+
|| (len == 2 && start[0] == '.' && start[1] == '.')) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Skip over the separator */
|
|
69
|
+
if (*p) {
|
|
70
|
+
p++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
char *JoinPath(const char *p1, const char *p2)
|
|
78
|
+
{
|
|
79
|
+
if (p1 == NULL || *p1 == '\0') {
|
|
80
|
+
APP_ERROR("p1 is null or empty");
|
|
81
|
+
return NULL;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (p2 == NULL || *p2 == '\0') {
|
|
85
|
+
APP_ERROR("p2 is null or empty");
|
|
86
|
+
return NULL;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
size_t p1_len = strlen(p1);
|
|
90
|
+
if (is_path_separator(p1[p1_len - 1])) { p1_len--; }
|
|
91
|
+
|
|
92
|
+
size_t p2_len = strlen(p2);
|
|
93
|
+
const char *p2_start = p2;
|
|
94
|
+
if (is_path_separator(*p2_start)) { p2_start++; p2_len--; }
|
|
95
|
+
|
|
96
|
+
size_t joined_len = p1_len + 1 + p2_len;
|
|
97
|
+
char *joined_path = calloc(1, joined_len + 1);
|
|
98
|
+
if (!joined_path) {
|
|
99
|
+
APP_ERROR("Failed to allocate buffer for join path");
|
|
100
|
+
return NULL;
|
|
101
|
+
}
|
|
102
|
+
memcpy(joined_path, p1, p1_len);
|
|
103
|
+
joined_path[p1_len] = PATH_SEPARATOR;
|
|
104
|
+
memcpy(joined_path + p1_len + 1, p2_start, p2_len);
|
|
105
|
+
joined_path[joined_len] = '\0';
|
|
106
|
+
|
|
107
|
+
return joined_path;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
char *GetParentPath(const char *path)
|
|
111
|
+
{
|
|
112
|
+
if (!path) {
|
|
113
|
+
APP_ERROR("path is NULL");
|
|
114
|
+
return NULL;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
size_t len = strlen(path);
|
|
118
|
+
size_t i = len;
|
|
119
|
+
|
|
120
|
+
/* Skip any trailing separators */
|
|
121
|
+
while (i > 0 && is_path_separator(path[i])) {
|
|
122
|
+
i--;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Skip the last segment's characters */
|
|
126
|
+
while (i > 0 && !is_path_separator(path[i])) {
|
|
127
|
+
i--;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* i==0 ⇒ empty parent */
|
|
131
|
+
|
|
132
|
+
char *out = malloc(i + 1);
|
|
133
|
+
if (!out) {
|
|
134
|
+
APP_ERROR("Memory allocation failed for parent path");
|
|
135
|
+
return NULL;
|
|
136
|
+
}
|
|
137
|
+
memcpy(out, path, i);
|
|
138
|
+
out[i] = '\0';
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ===== Memory-mapped file I/O ===== */
|
|
143
|
+
|
|
144
|
+
MemoryMap *CreateMemoryMap(const char *path) {
|
|
145
|
+
if (!path) {
|
|
146
|
+
FATAL("CreateMemoryMap: path is NULL");
|
|
147
|
+
return NULL;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
MemoryMap *map = malloc(sizeof(MemoryMap));
|
|
151
|
+
if (!map) {
|
|
152
|
+
FATAL("CreateMemoryMap: malloc failed");
|
|
153
|
+
return NULL;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
int fd = open(path, O_RDONLY);
|
|
157
|
+
if (fd < 0) {
|
|
158
|
+
FATAL("CreateMemoryMap: open(\"%s\") failed: %s", path, strerror(errno));
|
|
159
|
+
free(map);
|
|
160
|
+
return NULL;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
struct stat st;
|
|
164
|
+
if (fstat(fd, &st) < 0) {
|
|
165
|
+
FATAL("CreateMemoryMap: fstat failed: %s", strerror(errno));
|
|
166
|
+
close(fd);
|
|
167
|
+
free(map);
|
|
168
|
+
return NULL;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
size_t size = (size_t)st.st_size;
|
|
172
|
+
void *base = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
173
|
+
if (base == MAP_FAILED) {
|
|
174
|
+
FATAL("CreateMemoryMap: mmap failed: %s", strerror(errno));
|
|
175
|
+
close(fd);
|
|
176
|
+
free(map);
|
|
177
|
+
return NULL;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
map->fd = fd;
|
|
181
|
+
map->base = base;
|
|
182
|
+
map->size = size;
|
|
183
|
+
return map;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
void DestroyMemoryMap(MemoryMap *map) {
|
|
187
|
+
if (!map) {
|
|
188
|
+
FATAL("DestroyMemoryMap: map is NULL");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (map->base && map->size > 0) {
|
|
193
|
+
munmap(map->base, map->size);
|
|
194
|
+
}
|
|
195
|
+
if (map->fd >= 0) {
|
|
196
|
+
close(map->fd);
|
|
197
|
+
}
|
|
198
|
+
free(map);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
void *GetMemoryMapBase(const MemoryMap *map) {
|
|
202
|
+
return map ? map->base : NULL;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
size_t GetMemoryMapSize(const MemoryMap *map) {
|
|
206
|
+
return map ? map->size : 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* ===== File and directory operations ===== */
|
|
210
|
+
|
|
211
|
+
bool CreateDirectoriesRecursively(const char *dir) {
|
|
212
|
+
if (!dir || !*dir) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Check if directory already exists */
|
|
217
|
+
struct stat st;
|
|
218
|
+
if (stat(dir, &st) == 0) {
|
|
219
|
+
if (S_ISDIR(st.st_mode)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
FATAL("CreateDirectoriesRecursively: \"%s\" exists but is not a directory", dir);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Create parent directory recursively */
|
|
227
|
+
char *parent = GetParentPath(dir);
|
|
228
|
+
if (parent && *parent) {
|
|
229
|
+
if (!CreateDirectoriesRecursively(parent)) {
|
|
230
|
+
free(parent);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
free(parent);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* Create this directory */
|
|
237
|
+
if (mkdir(dir, 0755) < 0) {
|
|
238
|
+
if (errno == EEXIST) {
|
|
239
|
+
return true; /* Race condition: created by another thread */
|
|
240
|
+
}
|
|
241
|
+
FATAL("CreateDirectoriesRecursively: mkdir(\"%s\") failed: %s", dir, strerror(errno));
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
bool DeleteRecursively(const char *path) {
|
|
249
|
+
if (!path || !*path) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
struct stat st;
|
|
254
|
+
if (lstat(path, &st) < 0) {
|
|
255
|
+
FATAL("DeleteRecursively: stat(\"%s\") failed: %s", path, strerror(errno));
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!S_ISDIR(st.st_mode)) {
|
|
260
|
+
/* It's a file, just delete it */
|
|
261
|
+
if (unlink(path) < 0) {
|
|
262
|
+
FATAL("DeleteRecursively: unlink(\"%s\") failed: %s", path, strerror(errno));
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* It's a directory, delete contents recursively */
|
|
269
|
+
DIR *dir = opendir(path);
|
|
270
|
+
if (!dir) {
|
|
271
|
+
FATAL("DeleteRecursively: opendir(\"%s\") failed: %s", path, strerror(errno));
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
bool success = true;
|
|
276
|
+
struct dirent *entry;
|
|
277
|
+
while ((entry = readdir(dir)) != NULL) {
|
|
278
|
+
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
char *child_path = JoinPath(path, entry->d_name);
|
|
283
|
+
if (!child_path) {
|
|
284
|
+
success = false;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!DeleteRecursively(child_path)) {
|
|
289
|
+
free(child_path);
|
|
290
|
+
success = false;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
free(child_path);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
closedir(dir);
|
|
297
|
+
|
|
298
|
+
if (success) {
|
|
299
|
+
if (rmdir(path) < 0) {
|
|
300
|
+
FATAL("DeleteRecursively: rmdir(\"%s\") failed: %s", path, strerror(errno));
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return success;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
char *CreateUniqueDirectory(char *tmpl) {
|
|
309
|
+
if (!tmpl) {
|
|
310
|
+
return NULL;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
char *result = mkdtemp(tmpl);
|
|
314
|
+
if (!result) {
|
|
315
|
+
FATAL("CreateUniqueDirectory: mkdtemp failed: %s", strerror(errno));
|
|
316
|
+
return NULL;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
bool ExportFile(const char *path, const void *buffer, size_t buffer_size) {
|
|
323
|
+
if (!path || !buffer) {
|
|
324
|
+
FATAL("ExportFile: path or buffer is NULL");
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Create parent directories if needed */
|
|
329
|
+
char *parent = GetParentPath(path);
|
|
330
|
+
if (parent && *parent) {
|
|
331
|
+
if (!CreateDirectoriesRecursively(parent)) {
|
|
332
|
+
free(parent);
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
free(parent);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Create/overwrite the file */
|
|
339
|
+
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0777);
|
|
340
|
+
if (fd < 0) {
|
|
341
|
+
FATAL("ExportFile: open(\"%s\") failed: %s", path, strerror(errno));
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
size_t written = 0;
|
|
346
|
+
while (written < buffer_size) {
|
|
347
|
+
ssize_t n = write(fd, (const char *)buffer + written, buffer_size - written);
|
|
348
|
+
if (n < 0) {
|
|
349
|
+
FATAL("ExportFile: write(\"%s\") failed: %s", path, strerror(errno));
|
|
350
|
+
close(fd);
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
written += n;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
close(fd);
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* ===== Path utilities ===== */
|
|
361
|
+
|
|
362
|
+
char *GetImagePath(void) {
|
|
363
|
+
static char path_buffer[4096];
|
|
364
|
+
#ifdef __APPLE__
|
|
365
|
+
uint32_t size = sizeof(path_buffer);
|
|
366
|
+
if (_NSGetExecutablePath(path_buffer, &size) != 0) {
|
|
367
|
+
FATAL("GetImagePath: _NSGetExecutablePath failed");
|
|
368
|
+
return NULL;
|
|
369
|
+
}
|
|
370
|
+
#elif defined(__linux__)
|
|
371
|
+
/* On Linux, the running executable can be found via /proc/self/exe */
|
|
372
|
+
ssize_t len = readlink("/proc/self/exe", path_buffer, sizeof(path_buffer) - 1);
|
|
373
|
+
|
|
374
|
+
if (len < 0) {
|
|
375
|
+
FATAL("GetImagePath: readlink(\"/proc/self/exe\") failed: %s", strerror(errno));
|
|
376
|
+
return NULL;
|
|
377
|
+
}
|
|
378
|
+
path_buffer[len] = '\0';
|
|
379
|
+
#else
|
|
380
|
+
#error "GetImagePath not implemented for this platform"
|
|
381
|
+
#endif
|
|
382
|
+
|
|
383
|
+
char *result = malloc(strlen(path_buffer) + 1);
|
|
384
|
+
if (!result) {
|
|
385
|
+
FATAL("GetImagePath: malloc failed");
|
|
386
|
+
return NULL;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
strcpy(result, path_buffer);
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
char *GetTempDirectoryPath(void) {
|
|
394
|
+
const char *tmpdir = getenv("TMPDIR");
|
|
395
|
+
if (!tmpdir || !*tmpdir) {
|
|
396
|
+
tmpdir = "/tmp";
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
size_t len = strlen(tmpdir);
|
|
400
|
+
char *result = malloc(len + 1);
|
|
401
|
+
if (!result) {
|
|
402
|
+
FATAL("GetTempDirectoryPath: malloc failed");
|
|
403
|
+
return NULL;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
strcpy(result, tmpdir);
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* ===== Process and signal handling ===== */
|
|
411
|
+
|
|
412
|
+
bool InitializeSignalHandling(void) {
|
|
413
|
+
/* On POSIX systems, the parent process ignores SIGINT and SIGTERM during
|
|
414
|
+
initialization and cleanup. The child process will reset these to
|
|
415
|
+
SIG_DFL before execv-ing the target application. */
|
|
416
|
+
|
|
417
|
+
struct sigaction sa;
|
|
418
|
+
memset(&sa, 0, sizeof(sa));
|
|
419
|
+
sa.sa_handler = SIG_IGN;
|
|
420
|
+
|
|
421
|
+
if (sigaction(SIGINT, &sa, NULL) < 0) {
|
|
422
|
+
FATAL("InitializeSignalHandling: sigaction(SIGINT) failed: %s", strerror(errno));
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (sigaction(SIGTERM, &sa, NULL) < 0) {
|
|
427
|
+
FATAL("InitializeSignalHandling: sigaction(SIGTERM) failed: %s", strerror(errno));
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
bool SetEnvVar(const char *name, const char *value) {
|
|
435
|
+
if (!name) {
|
|
436
|
+
FATAL("SetEnvVar: name is NULL");
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (value == NULL) {
|
|
441
|
+
/* Remove the variable */
|
|
442
|
+
if (unsetenv(name) < 0) {
|
|
443
|
+
FATAL("SetEnvVar: unsetenv(\"%s\") failed: %s", name, strerror(errno));
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
if (setenv(name, value, 1) < 0) {
|
|
448
|
+
FATAL("SetEnvVar: setenv(\"%s\", ...) failed: %s", name, strerror(errno));
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
bool CreateAndWaitForProcess(const char *app_name, char *argv[], int *exit_code) {
|
|
457
|
+
if (!app_name || !argv || !exit_code) {
|
|
458
|
+
FATAL("CreateAndWaitForProcess: app_name, argv, or exit_code is NULL");
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
pid_t pid = fork();
|
|
463
|
+
if (pid < 0) {
|
|
464
|
+
FATAL("CreateAndWaitForProcess: fork() failed: %s", strerror(errno));
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (pid == 0) {
|
|
469
|
+
/* Child process */
|
|
470
|
+
|
|
471
|
+
/* Reset signal handlers to default before exec */
|
|
472
|
+
signal(SIGINT, SIG_DFL);
|
|
473
|
+
signal(SIGTERM, SIG_DFL);
|
|
474
|
+
|
|
475
|
+
/* Execute the target application */
|
|
476
|
+
execv(app_name, argv);
|
|
477
|
+
|
|
478
|
+
/* If we get here, execv failed */
|
|
479
|
+
FATAL("CreateAndWaitForProcess: execv(\"%s\") failed: %s", app_name, strerror(errno));
|
|
480
|
+
exit(127);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* Parent process */
|
|
484
|
+
|
|
485
|
+
int wstatus;
|
|
486
|
+
if (waitpid(pid, &wstatus, 0) < 0) {
|
|
487
|
+
FATAL("CreateAndWaitForProcess: waitpid failed: %s", strerror(errno));
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (WIFEXITED(wstatus)) {
|
|
492
|
+
*exit_code = WEXITSTATUS(wstatus);
|
|
493
|
+
} else if (WIFSIGNALED(wstatus)) {
|
|
494
|
+
*exit_code = 128 + WTERMSIG(wstatus);
|
|
495
|
+
} else {
|
|
496
|
+
*exit_code = 1;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return true;
|
|
500
|
+
}
|