libzip.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 
26 #include "libzip.hpp"
27 #include "casadi/core/filesystem_impl.hpp"
28 #include <zip.h>
29 #include <cstring>
30 namespace casadi {
31 
32  bool extract_zip_internal(zip_t* za, const std::string& output_dir); // Declare before use
33 
34  bool extract_zip_from_stringstream(std::stringstream& src, const std::string& output_dir) {
35  src.clear();
36  src.seekg(0, std::ios::beg);
37  const std::string& s = src.str();
38  // Open zip archive from memory buffer
39  zip_error_t errorp;
40  zip_source_t* zip_src = zip_source_buffer_create(s.data(), s.size(), 0, &errorp);
41 
42  if (!zip_src) {
43  casadi_error("Failed to create zip source: " +
44  std::string(zip_error_strerror(&errorp)) + "\n");
45  return false;
46  }
47 
48  zip_t* archive = zip_open_from_source(zip_src, 0, &errorp);
49  if (!archive) {
50  zip_source_free(zip_src);
51  casadi_error("Failed to open zip from source: " +
52  std::string(zip_error_strerror(&errorp)) + "\n");
53  }
54  return extract_zip_internal(archive, output_dir);
55  }
56 
57  bool extract_zip_from_path(const std::string& zip_path, const std::string& output_dir) {
58  int err;
59  zip_t* za = zip_open(zip_path.c_str(), ZIP_RDONLY, &err);
60  if (!za) {
61  casadi_error("Cannot open ZIP file: " + zip_path);
62  return false;
63  }
64  return extract_zip_internal(za, output_dir);
65  }
66 
67  bool extract_zip_internal(zip_t* za, const std::string& output_dir) {
69  auto filesystem = Filesystem::getPlugin("ghc");
70 
71  zip_int64_t num_entries = zip_get_num_entries(za, 0);
72  if (num_entries < 0) {
73  zip_close(za);
74  casadi_error("Cannot read ZIP contents.");
75  return false;
76  }
77 
78 
79  for (zip_uint64_t i = 0; i < static_cast<zip_uint64_t>(num_entries); ++i) {
80  const char* name = zip_get_name(za, i, 0);
81  if (!name) {
82  uerr() << "Error: Cannot get file name for entry " << i << std::endl;
83  continue;
84  }
85 
86  std::string full_path = output_dir + "/" + name;
87  if (full_path.back() == '/') { // Directory entry
88  filesystem.exposed.create_directories(full_path);
89  } else { // File entry
90  std::string dir_path = full_path.substr(0, full_path.find_last_of('/'));
91  filesystem.exposed.create_directories(dir_path);
92 
93  zip_file_t* zf = zip_fopen_index(za, i, 0);
94  if (!zf) {
95  uerr() << "Error: Cannot open file in ZIP: " << name << std::endl;
96  continue;
97  }
98 
99  std::ofstream out_file(full_path, std::ios::binary);
100  if (!out_file) {
101  uerr() << "Error: Cannot write file: " << full_path << std::endl;
102  zip_fclose(zf);
103  continue;
104  }
105 
106  char buffer[8192];
107  zip_int64_t bytes_read;
108  while ((bytes_read = zip_fread(zf, buffer, sizeof(buffer))) > 0) {
109  out_file.write(buffer, bytes_read);
110  }
111 
112  if (bytes_read < 0) {
113  uerr() << "Error: Read failed for file in ZIP: " << name << std::endl;
114  }
115 
116  out_file.close();
117  zip_fclose(zf);
118  }
119  }
120 
121  zip_close(za);
122  return true;
123  }
124 
125  bool add_file_to_zip(zip_t* archive, const std::string& file_path,
126  const std::string& archive_name) {
127  std::ifstream file(file_path, std::ios::binary | std::ios::ate);
128  if (!file) {
129  uerr() << "Error: Cannot open file: " << file_path << std::endl;
130  return false;
131  }
132 
133  std::streamsize size = file.tellg();
134  file.seekg(0, std::ios::beg);
135 
136  char* data = static_cast<char*>(malloc(size)); // ← use malloc
137  if (!data) {
138  uerr() << "Error: Memory allocation failed for file: " << file_path << std::endl;
139  return false;
140  }
141 
142  if (!file.read(data, size)) {
143  uerr() << "Error: Cannot read file: " << file_path << std::endl;
144  free(data);
145  return false;
146  }
147 
148  zip_error_t ziperr;
149  zip_source_t* source = zip_source_buffer_create(data, size, 1, &ziperr); // ← freep = 1
150  if (!source) {
151  uerr() << "Error: Cannot create zip source for file: " << file_path
152  << ": " << zip_error_strerror(&ziperr) << std::endl;
153  free(data); // not strictly needed, but safe
154  zip_error_fini(&ziperr);
155  return false;
156  }
157 
158  zip_int64_t idx = zip_file_add(archive, archive_name.c_str(), source, ZIP_FL_ENC_UTF_8);
159  if (idx < 0) {
160  zip_source_free(source); // Only needed if not added
161  uerr() << "Error: Cannot add file to archive: " << archive_name << std::endl;
162  return false;
163  }
164 
165  return true;
166  }
167 
168  void add_directory_recursive(zip_t* archive,
169  const std::string& base_dir,
170  const std::string& current_dir,
171  const std::string& rel_prefix) {
172  auto filesystem = Filesystem::getPlugin("ghc");
173  std::vector<std::string> entries = filesystem.exposed.iterate_directory_names(current_dir);
174 
175  for (const std::string& full_path : entries) {
176  std::string rel_path = full_path.substr(base_dir.size() + 1);
177 
178  if (filesystem.exposed.is_directory(full_path)) {
179  zip_dir_add(archive, (rel_path + "/").c_str(), ZIP_FL_ENC_UTF_8);
180  add_directory_recursive(archive, base_dir, full_path, rel_path);
181  } else {
182  add_file_to_zip(archive, full_path, rel_path);
183  }
184  }
185  }
186  bool zip_to_stream(const std::string& dir, std::ostream& output) {
187  zip_error_t error;
188  zip_error_init(&error);
189 
190  zip_source_t* src = zip_source_buffer_create(nullptr, 0, 0, &error);
191  if (!src) {
192  uerr() << "Failed to create zip source buffer: "
193  << zip_error_strerror(&error) << std::endl;
194  zip_error_fini(&error);
195  return false;
196  }
197 
198  // Prevent zip_close from destroying the source
199  zip_source_keep(src);
200 
201  zip_t* archive = zip_open_from_source(src, ZIP_TRUNCATE, &error);
202  if (!archive) {
203  uerr() << "Failed to open zip archive from source: "
204  << zip_error_strerror(&error) << std::endl;
205  zip_source_free(src);
206  zip_error_fini(&error);
207  return false;
208  }
209 
210  try {
211  add_directory_recursive(archive, dir, dir, "");
212  } catch (const std::exception& e) {
213  uerr() << "Exception while zipping directory: " << e.what() << std::endl;
214  zip_discard(archive); // also frees src
215  zip_error_fini(&error);
216  return false;
217  }
218 
219  if (zip_close(archive) != 0) {
220  uerr() << "Failed to finalize zip archive: "
221  << zip_error_strerror(&error) << std::endl;
222  zip_source_free(src);
223  zip_error_fini(&error);
224  return false;
225  }
226 
227  // At this point, src contains the archive in memory.
228  if (zip_source_open(src) < 0) {
229  uerr() << "Failed to open zip source for reading." << std::endl;
230  zip_source_free(src);
231  zip_error_fini(&error);
232  return false;
233  }
234 
235  // Seek to end to get size
236  if (zip_source_seek(src, 0, SEEK_END) < 0) {
237  uerr() << "Failed to seek to end of zip source." << std::endl;
238  zip_source_close(src);
239  zip_source_free(src);
240  zip_error_fini(&error);
241  return false;
242  }
243 
244  zip_int64_t size = zip_source_tell(src);
245  if (size < 0) {
246  uerr() << "Failed to get size of zip source." << std::endl;
247  zip_source_close(src);
248  zip_source_free(src);
249  zip_error_fini(&error);
250  return false;
251  }
252 
253  if (zip_source_seek(src, 0, SEEK_SET) < 0) {
254  uerr() << "Failed to rewind zip source." << std::endl;
255  zip_source_close(src);
256  zip_source_free(src);
257  zip_error_fini(&error);
258  return false;
259  }
260 
261  if (zip_source_seek(src, 0, SEEK_SET) < 0) {
262  uerr() << "Failed to rewind zip source." << std::endl;
263  zip_source_close(src);
264  zip_source_free(src);
265  zip_error_fini(&error);
266  return false;
267  }
268 
269  // Efficient streaming read/write
270  char buf[8192];
271  zip_int64_t bytes_read;
272 
273  while ((bytes_read = zip_source_read(src, buf, sizeof(buf))) > 0) {
274  output.write(buf, bytes_read);
275  if (!output) {
276  uerr() << "Write error while streaming zip data to output." << std::endl;
277  zip_source_close(src);
278  zip_source_free(src);
279  zip_error_fini(&error);
280  return false;
281  }
282  }
283 
284  zip_source_close(src);
285  zip_source_free(src);
286  zip_error_fini(&error);
287 
288  if (bytes_read < 0) {
289  uerr() << "Error reading from zip source." << std::endl;
290  return false;
291  }
292 
293  return true;
294  }
295 
296 
297  bool zip_to_path(const std::string& dir_path, const std::string& zip_path) {
298  std::ofstream ofs(zip_path, std::ios::binary);
299  if (!ofs) {
300  uerr() << "Failed to open output file: " << zip_path << std::endl;
301  return false;
302  }
303 
304  return zip_to_stream(dir_path, ofs);
305  }
306 
307  bool zip_to_path2(const std::string& dir_path, const std::string& zip_path) {
308  int errorp;
309  zip_t* archive = zip_open(zip_path.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorp);
310  if (!archive) {
311  zip_error_t ziperror;
312  zip_error_init_with_code(&ziperror, errorp);
313  uerr() << "Error: Cannot open zip archive " << zip_path << ": "
314  << zip_error_strerror(&ziperror) << std::endl;
315  zip_error_fini(&ziperror);
316  return false;
317  }
318 
319  try {
320  add_directory_recursive(archive, dir_path, dir_path, "");
321  } catch (const std::exception& e) {
322  uerr() << "Exception while zipping directory: " << e.what() << std::endl;
323  zip_discard(archive);
324  return false;
325  }
326 
327  if (zip_close(archive) < 0) {
328  uerr() << "Error: Cannot finalize zip archive: " << zip_strerror(archive) << std::endl;
329  zip_discard(archive);
330  return false;
331  }
332 
333  return true;
334  }
335 
336  extern "C"
337  int CASADI_ARCHIVER_LIBZIP_EXPORT
338  casadi_register_archiver_libzip(Archiver::Plugin* plugin) {
339  plugin->name = "libzip";
340  plugin->doc = Libzip::meta_doc.c_str();
341  plugin->version = CASADI_VERSION;
342  plugin->exposed.unpack = &extract_zip_from_path;
343  plugin->exposed.unpack_from_stringstream = &extract_zip_from_stringstream;
344  plugin->exposed.pack = &zip_to_path;
345  plugin->exposed.pack_to_stream = &zip_to_stream;
346  return 0;
347  }
348 
349  extern "C"
350  void CASADI_ARCHIVER_LIBZIP_EXPORT casadi_load_archiver_libzip() {
352  }
353 
354 
355 } // namespace casadi
static void assert_enabled()
Definition: filesystem.cpp:87
static const std::string meta_doc
A documentation string.
Definition: libzip.hpp:53
static Plugin & getPlugin(const std::string &pname)
Load and get the creator function.
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
The casadi namespace.
Definition: archiver.cpp:28
std::ostream & uerr()
int CASADI_ARCHIVER_LIBZIP_EXPORT casadi_register_archiver_libzip(Archiver::Plugin *plugin)
Definition: libzip.cpp:338
bool zip_to_path(const std::string &dir_path, const std::string &zip_path)
Definition: libzip.cpp:297
bool extract_zip_from_path(const std::string &zip_path, const std::string &output_dir)
Definition: libzip.cpp:57
bool extract_zip_internal(zip_t *za, const std::string &output_dir)
Definition: libzip.cpp:67
bool add_file_to_zip(zip_t *archive, const std::string &file_path, const std::string &archive_name)
Definition: libzip.cpp:125
bool zip_to_stream(const std::string &dir, std::ostream &output)
Definition: libzip.cpp:186
void add_directory_recursive(zip_t *archive, const std::string &base_dir, const std::string &current_dir, const std::string &rel_prefix)
Definition: libzip.cpp:168
bool extract_zip_from_stringstream(std::stringstream &src, const std::string &output_dir)
Definition: libzip.cpp:34
void CASADI_ARCHIVER_LIBZIP_EXPORT casadi_load_archiver_libzip()
Definition: libzip.cpp:350
bool zip_to_path2(const std::string &dir_path, const std::string &zip_path)
Definition: libzip.cpp:307