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  auto out_file_ptr = Filesystem::ofstream_ptr(full_path, std::ios::binary);
100  std::ostream& out_file = *out_file_ptr;
101  if (!out_file) {
102  uerr() << "Error: Cannot write file: " << full_path << std::endl;
103  zip_fclose(zf);
104  continue;
105  }
106 
107  char buffer[8192];
108  zip_int64_t bytes_read;
109  while ((bytes_read = zip_fread(zf, buffer, sizeof(buffer))) > 0) {
110  out_file.write(buffer, bytes_read);
111  }
112 
113  if (bytes_read < 0) {
114  uerr() << "Error: Read failed for file in ZIP: " << name << std::endl;
115  }
116 
117  out_file_ptr.reset();
118  zip_fclose(zf);
119  }
120  }
121 
122  zip_close(za);
123  return true;
124  }
125 
126  bool add_file_to_zip(zip_t* archive, const std::string& file_path,
127  const std::string& archive_name) {
128  auto file_ptr = Filesystem::ifstream_ptr(file_path, std::ios::binary | std::ios::ate);
129  std::istream& file = *file_ptr;
130  if (!file) {
131  uerr() << "Error: Cannot open file: " << file_path << std::endl;
132  return false;
133  }
134 
135  std::streamsize size = file.tellg();
136  file.seekg(0, std::ios::beg);
137 
138  char* data = static_cast<char*>(malloc(size)); // ← use malloc
139  if (!data) {
140  uerr() << "Error: Memory allocation failed for file: " << file_path << std::endl;
141  return false;
142  }
143 
144  if (!file.read(data, size)) {
145  uerr() << "Error: Cannot read file: " << file_path << std::endl;
146  free(data);
147  return false;
148  }
149 
150  zip_error_t ziperr;
151  zip_source_t* source = zip_source_buffer_create(data, size, 1, &ziperr); // ← freep = 1
152  if (!source) {
153  uerr() << "Error: Cannot create zip source for file: " << file_path
154  << ": " << zip_error_strerror(&ziperr) << std::endl;
155  free(data); // not strictly needed, but safe
156  zip_error_fini(&ziperr);
157  return false;
158  }
159 
160  zip_int64_t idx = zip_file_add(archive, archive_name.c_str(), source, ZIP_FL_ENC_UTF_8);
161  if (idx < 0) {
162  zip_source_free(source); // Only needed if not added
163  uerr() << "Error: Cannot add file to archive: " << archive_name << std::endl;
164  return false;
165  }
166 
167  return true;
168  }
169 
170  void add_directory_recursive(zip_t* archive,
171  const std::string& base_dir,
172  const std::string& current_dir,
173  const std::string& rel_prefix) {
174  auto filesystem = Filesystem::getPlugin("ghc");
175  std::vector<std::string> entries = filesystem.exposed.iterate_directory_names(current_dir);
176 
177  for (const std::string& full_path : entries) {
178  std::string rel_path = full_path.substr(base_dir.size() + 1);
179 
180  if (filesystem.exposed.is_directory(full_path)) {
181  zip_dir_add(archive, (rel_path + "/").c_str(), ZIP_FL_ENC_UTF_8);
182  add_directory_recursive(archive, base_dir, full_path, rel_path);
183  } else {
184  add_file_to_zip(archive, full_path, rel_path);
185  }
186  }
187  }
188  bool zip_to_stream(const std::string& dir, std::ostream& output) {
189  zip_error_t error;
190  zip_error_init(&error);
191 
192  zip_source_t* src = zip_source_buffer_create(nullptr, 0, 0, &error);
193  if (!src) {
194  uerr() << "Failed to create zip source buffer: "
195  << zip_error_strerror(&error) << std::endl;
196  zip_error_fini(&error);
197  return false;
198  }
199 
200  // Prevent zip_close from destroying the source
201  zip_source_keep(src);
202 
203  zip_t* archive = zip_open_from_source(src, ZIP_TRUNCATE, &error);
204  if (!archive) {
205  uerr() << "Failed to open zip archive from source: "
206  << zip_error_strerror(&error) << std::endl;
207  zip_source_free(src);
208  zip_error_fini(&error);
209  return false;
210  }
211 
212  try {
213  add_directory_recursive(archive, dir, dir, "");
214  } catch (const std::exception& e) {
215  uerr() << "Exception while zipping directory: " << e.what() << std::endl;
216  zip_discard(archive); // also frees src
217  zip_error_fini(&error);
218  return false;
219  }
220 
221  if (zip_close(archive) != 0) {
222  uerr() << "Failed to finalize zip archive: "
223  << zip_error_strerror(&error) << std::endl;
224  zip_source_free(src);
225  zip_error_fini(&error);
226  return false;
227  }
228 
229  // At this point, src contains the archive in memory.
230  if (zip_source_open(src) < 0) {
231  uerr() << "Failed to open zip source for reading." << std::endl;
232  zip_source_free(src);
233  zip_error_fini(&error);
234  return false;
235  }
236 
237  // Seek to end to get size
238  if (zip_source_seek(src, 0, SEEK_END) < 0) {
239  uerr() << "Failed to seek to end of zip source." << std::endl;
240  zip_source_close(src);
241  zip_source_free(src);
242  zip_error_fini(&error);
243  return false;
244  }
245 
246  zip_int64_t size = zip_source_tell(src);
247  if (size < 0) {
248  uerr() << "Failed to get size of zip source." << std::endl;
249  zip_source_close(src);
250  zip_source_free(src);
251  zip_error_fini(&error);
252  return false;
253  }
254 
255  if (zip_source_seek(src, 0, SEEK_SET) < 0) {
256  uerr() << "Failed to rewind zip source." << std::endl;
257  zip_source_close(src);
258  zip_source_free(src);
259  zip_error_fini(&error);
260  return false;
261  }
262 
263  if (zip_source_seek(src, 0, SEEK_SET) < 0) {
264  uerr() << "Failed to rewind zip source." << std::endl;
265  zip_source_close(src);
266  zip_source_free(src);
267  zip_error_fini(&error);
268  return false;
269  }
270 
271  // Efficient streaming read/write
272  char buf[8192];
273  zip_int64_t bytes_read;
274 
275  while ((bytes_read = zip_source_read(src, buf, sizeof(buf))) > 0) {
276  output.write(buf, bytes_read);
277  if (!output) {
278  uerr() << "Write error while streaming zip data to output." << std::endl;
279  zip_source_close(src);
280  zip_source_free(src);
281  zip_error_fini(&error);
282  return false;
283  }
284  }
285 
286  zip_source_close(src);
287  zip_source_free(src);
288  zip_error_fini(&error);
289 
290  if (bytes_read < 0) {
291  uerr() << "Error reading from zip source." << std::endl;
292  return false;
293  }
294 
295  return true;
296  }
297 
298 
299  bool zip_to_path(const std::string& dir_path, const std::string& zip_path) {
300  auto ofs_ptr = Filesystem::ofstream_ptr(zip_path, std::ios::binary);
301  std::ostream& ofs = *ofs_ptr;
302  if (!ofs) {
303  uerr() << "Failed to open output file: " << zip_path << std::endl;
304  return false;
305  }
306 
307  return zip_to_stream(dir_path, ofs);
308  }
309 
310  bool zip_to_path2(const std::string& dir_path, const std::string& zip_path) {
311  int errorp;
312  zip_t* archive = zip_open(zip_path.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &errorp);
313  if (!archive) {
314  zip_error_t ziperror;
315  zip_error_init_with_code(&ziperror, errorp);
316  uerr() << "Error: Cannot open zip archive " << zip_path << ": "
317  << zip_error_strerror(&ziperror) << std::endl;
318  zip_error_fini(&ziperror);
319  return false;
320  }
321 
322  try {
323  add_directory_recursive(archive, dir_path, dir_path, "");
324  } catch (const std::exception& e) {
325  uerr() << "Exception while zipping directory: " << e.what() << std::endl;
326  zip_discard(archive);
327  return false;
328  }
329 
330  if (zip_close(archive) < 0) {
331  uerr() << "Error: Cannot finalize zip archive: " << zip_strerror(archive) << std::endl;
332  zip_discard(archive);
333  return false;
334  }
335 
336  return true;
337  }
338 
339  extern "C"
340  int CASADI_ARCHIVER_LIBZIP_EXPORT
341  casadi_register_archiver_libzip(Archiver::Plugin* plugin) {
342  plugin->name = "libzip";
343  plugin->doc = Libzip::meta_doc.c_str();
344  plugin->version = CASADI_VERSION;
345  plugin->exposed.unpack = &extract_zip_from_path;
346  plugin->exposed.unpack_from_stringstream = &extract_zip_from_stringstream;
347  plugin->exposed.pack = &zip_to_path;
348  plugin->exposed.pack_to_stream = &zip_to_stream;
349  return 0;
350  }
351 
352  extern "C"
353  void CASADI_ARCHIVER_LIBZIP_EXPORT casadi_load_archiver_libzip() {
355  }
356 
357 
358 } // namespace casadi
static std::unique_ptr< std::ostream > ofstream_ptr(const std::string &path, std::ios_base::openmode mode=std::ios_base::out)
Definition: filesystem.cpp:115
static std::unique_ptr< std::istream > ifstream_ptr(const std::string &path, std::ios_base::openmode mode=std::ios_base::in, bool fail=true)
Definition: filesystem.cpp:135
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:341
bool zip_to_path(const std::string &dir_path, const std::string &zip_path)
Definition: libzip.cpp:299
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:126
bool zip_to_stream(const std::string &dir, std::ostream &output)
Definition: libzip.cpp:188
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:170
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:353
bool zip_to_path2(const std::string &dir_path, const std::string &zip_path)
Definition: libzip.cpp:310