1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
|
/*!A cross-platform build utility based on Lua
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright (C) 2015-present, Xmake Open Source Community.
*
* @author ruki
* @file extractlib.c
*
*/
/* //////////////////////////////////////////////////////////////////////////////////////
* trace
*/
#define TB_TRACE_MODULE_NAME "extractlib"
#define TB_TRACE_MODULE_DEBUG (0)
/* //////////////////////////////////////////////////////////////////////////////////////
* includes
*/
#include "prefix.h"
/* //////////////////////////////////////////////////////////////////////////////////////
* implementation
*/
/* generate unique filename to handle name conflicts
*
* @param base_name the base filename
* @param id the unique ID
* @param output output buffer
* @param output_size size of output buffer
* @param output_len output: actual output length
* @return tb_true on success
*/
static tb_bool_t xm_binutils_ar_generate_unique_name(tb_char_t const *base_name, tb_uint32_t id, tb_char_t *output, tb_size_t output_size, tb_size_t* output_len) {
tb_assert_and_check_return_val(base_name && output && output_size > 0 && output_len, tb_false);
// find the last dot for extension
tb_char_t const *ext = tb_strrchr(base_name, '.');
tb_long_t n = -1;
if (ext) {
tb_size_t base_len = (tb_size_t)(ext - base_name);
tb_size_t ext_len = tb_strlen(ext);
if (base_len + ext_len + 16 < output_size) {
n = tb_snprintf(output, output_size, "%.*s_%u%s", (tb_int_t)base_len, base_name, id, ext);
}
} else {
// no extension
if (tb_strlen(base_name) + 16 < output_size) {
n = tb_snprintf(output, output_size, "%s_%u", base_name, id);
}
}
if (n >= 0) {
*output_len = (tb_size_t)n;
return tb_true;
}
return tb_false;
}
/* extract AR archive to directory
*
* @param istream the input stream
* @param outputdir the output directory
* @return tb_true on success, tb_false on failure
*/
tb_bool_t xm_binutils_ar_extract(tb_stream_ref_t istream, tb_char_t const *outputdir) {
tb_assert_and_check_return_val(istream && outputdir, tb_false);
// get output directory length
tb_size_t outputdir_len = tb_strlen(outputdir);
// check AR magic (!<arch>\n)
if (!xm_binutils_ar_check_magic(istream, 0)) {
return tb_false;
}
// ensure output directory exists
// check if directory already exists
if (!tb_file_info(outputdir, tb_null)) {
// directory doesn't exist, create it
if (!tb_directory_create(outputdir)) {
return tb_false;
}
}
tb_bool_t ok = tb_true;
// iterate through AR members
while (ok) {
// read AR header
// AR header is exactly 60 bytes: name[16] + date[12] + uid[6] + gid[6] + mode[8] + size[10] + fmag[2]
xm_ar_header_t header;
if (!tb_stream_bread(istream, (tb_byte_t*)&header, sizeof(header))) {
// end of file
break;
}
// parse member size
tb_int64_t member_size = xm_binutils_ar_parse_decimal(header.size, 10);
if (member_size < 0) {
ok = tb_false;
break;
}
// get member name
tb_char_t member_name[256] = {0};
tb_size_t name_len = 0;
tb_hize_t name_bytes_read = 0;
// get member name (handles both regular and extended name formats)
tb_bool_t skip = tb_false;
if (!xm_binutils_ar_get_member_name(istream, &header, member_name, sizeof(member_name), &name_len, &name_bytes_read)) {
skip = tb_true;
} else if (xm_binutils_ar_is_symbol_table(member_name)) {
// skip symbol tables
skip = tb_true;
} else if (!xm_binutils_ar_is_object_file(member_name)) {
// only extract object files
skip = tb_true;
}
if (skip) {
// skip remaining data + padding using sequential read
tb_hize_t skip_size = (tb_hize_t)member_size - name_bytes_read;
if (member_size % 2) {
skip_size++; // add padding
}
if (!tb_stream_skip(istream, skip_size)) {
ok = tb_false;
break;
}
continue;
}
// handle name conflicts by checking if file exists and renaming with ID
tb_char_t output_name[512] = {0};
tb_char_t output_path_check[1024] = {0};
if (outputdir_len + 1 + name_len >= sizeof(output_path_check)) {
ok = tb_false;
break;
}
tb_snprintf(output_path_check, sizeof(output_path_check), "%s/%s", outputdir, member_name);
// check if file already exists
tb_uint32_t conflict_id = 1;
tb_size_t output_name_len = name_len;
if (tb_file_info(output_path_check, tb_null)) {
// name conflict, try different IDs until we find an available name
while (conflict_id < 10000) { // reasonable limit
if (!xm_binutils_ar_generate_unique_name(member_name, conflict_id, output_name, sizeof(output_name), &output_name_len)) {
ok = tb_false;
break;
}
if (outputdir_len + 1 + output_name_len >= sizeof(output_path_check)) {
ok = tb_false;
break;
}
tb_snprintf(output_path_check, sizeof(output_path_check), "%s/%s", outputdir, output_name);
if (!tb_file_info(output_path_check, tb_null)) {
// found available name
break;
}
conflict_id++;
}
if (conflict_id >= 10000) {
ok = tb_false;
break;
}
} else {
// first occurrence, use original name
tb_strlcpy(output_name, member_name, sizeof(output_name));
}
// build output path
tb_char_t output_path[1024] = {0};
if (outputdir_len + 1 + output_name_len >= sizeof(output_path)) {
ok = tb_false;
break;
}
tb_snprintf(output_path, sizeof(output_path), "%s/%s", outputdir, output_name);
// create output file
tb_stream_ref_t ostream = tb_stream_init_from_file(output_path, TB_FILE_MODE_RW | TB_FILE_MODE_CREAT | TB_FILE_MODE_TRUNC);
if (!ostream) {
ok = tb_false;
break;
}
if (!tb_stream_open(ostream)) {
tb_stream_exit(ostream);
ok = tb_false;
break;
}
// copy member data to output file
// member_size includes the name if extended format was used, so subtract name_bytes_read
tb_hize_t remaining = (tb_hize_t)member_size - name_bytes_read;
if (!xm_binutils_stream_copy(istream, ostream, remaining)) {
ok = tb_false;
}
tb_stream_clos(ostream);
tb_stream_exit(ostream);
tb_check_break(ok);
// align to 2-byte boundary (AR format requirement)
if (member_size % 2) {
if (!tb_stream_skip(istream, 1)) {
ok = tb_false;
break;
}
}
}
return ok;
}
|