GNU Octave 7.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
gzip.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2016-2022 The Octave Project Developers
4//
5// See the file COPYRIGHT.md in the top-level directory of this
6// distribution or <https://octave.org/copyright/>.
7//
8// This file is part of Octave.
9//
10// Octave is free software: you can redistribute it and/or modify it
11// under the terms of the GNU General Public License as published by
12// the Free Software Foundation, either version 3 of the License, or
13// (at your option) any later version.
14//
15// Octave is distributed in the hope that it will be useful, but
16// WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with Octave; see the file COPYING. If not, see
22// <https://www.gnu.org/licenses/>.
23//
24////////////////////////////////////////////////////////////////////////
25
26//! @file gzip.cc
27//! Octave interface to the compression and uncompression libraries.
28//!
29//! This was originally implemented as an m file which directly called
30//! bzip2 and gzip applications. This may look simpler but causes some
31//! issues (see bug #43431) because we have no control over the output
32//! file:
33//!
34//! - created file is always in the same directory as the original file;
35//! - automatically skip files that already have gz/bz2/etc extension;
36//! - some older versions lack the --keep option.
37//!
38//! In addition, because system() does not have a method that allows
39//! passing a list of arguments, there is the issue of having to escape
40//! filenames.
41//!
42//! A solution is to pipe file contents into the applications instead of
43//! filenames. However, that solution causes:
44//!
45//! # missing file header with original file information;
46//! # implementing ourselves the recursive transversion of directories;
47//! # do the above in a m file which will be slow;
48//! # popen2 is frail on windows.
49
50#if defined (HAVE_CONFIG_H)
51# include "config.h"
52#endif
53
54#include <cstdio>
55#include <cstring>
56
57#include <functional>
58#include <list>
59#include <stdexcept>
60#include <string>
61
62#include "Array.h"
63#include "dir-ops.h"
64#include "file-ops.h"
65#include "file-stat.h"
66#include "glob-match.h"
67#include "lo-sysdep.h"
68#include "oct-env.h"
69#include "str-vec.h"
70
71#include "Cell.h"
72#include "defun-dld.h"
73#include "defun-int.h"
74#include "errwarn.h"
75#include "ov.h"
76#include "ovl.h"
77
78#if defined (HAVE_BZLIB_H)
79# include <bzlib.h>
80#endif
81
82#if defined (HAVE_ZLIB_H)
83# include <zlib.h>
84#endif
85
86OCTAVE_NAMESPACE_BEGIN
87
88 //! RIIA wrapper for std::FILE*.
89 //!
90 //! If error handling is available for failing to close the file, use
91 //! the close method which throws.
92 //!
93 //! If the file has been closed, fp is set to nullptr. Remember that
94 //! behavior is undefined if the value of the pointer stream is used
95 //! after fclose.
96
97 class CFile
98 {
99 public:
100
101 CFile (void) = delete;
102
103 CFile (const std::string& path, const std::string& mode)
104 : m_fp (sys::fopen (path, mode))
105 {
106 if (! m_fp)
107 throw std::runtime_error ("unable to open file");
108 }
109
110 CFile (const CFile&) = delete;
111
112 CFile& operator = (const CFile&) = delete;
113
114 ~CFile (void)
115 {
116 if (m_fp)
117 std::fclose (m_fp);
118 }
119
120 void close (void)
121 {
122 if (std::fclose (m_fp))
123 throw std::runtime_error ("unable to close file");
124
125 m_fp = nullptr;
126 }
127
128 std::FILE *m_fp;
129 };
130
131#if defined (HAVE_BZ2)
132
133 class bz2
134 {
135 public:
136
137 static const constexpr char *extension = ".bz2";
138
139 static void zip (const std::string& source_path,
140 const std::string& dest_path)
141 {
142 bz2::zipper z (source_path, dest_path);
143 z.deflate ();
144 z.close ();
145 }
146
147 private:
148
149 class zipper
150 {
151 public:
152
153 zipper (void) = delete;
154
155 zipper (const std::string& source_path, const std::string& dest_path)
156 : m_status (BZ_OK), m_source (source_path, "rb"),
157 m_dest (dest_path, "wb"),
158 m_bz (BZ2_bzWriteOpen (&m_status, m_dest.m_fp, 9, 0, 30))
159 {
160 if (m_status != BZ_OK)
161 throw std::runtime_error ("failed to open bzip2 stream");
162 }
163
164 zipper (const zipper&) = delete;
165
166 zipper& operator = (const zipper&) = delete;
167
168 ~zipper (void)
169 {
170 if (m_bz != nullptr)
171 BZ2_bzWriteClose (&m_status, m_bz, 1, nullptr, nullptr);
172 }
173
174 void deflate (void)
175 {
176 const std::size_t buf_len = 8192;
177 char buf[buf_len];
178 std::size_t n_read;
179 while ((n_read = std::fread (buf, sizeof (buf[0]), buf_len, m_source.m_fp)) != 0)
180 {
181 if (std::ferror (m_source.m_fp))
182 throw std::runtime_error ("failed to read from source file");
183 BZ2_bzWrite (&m_status, m_bz, buf, n_read);
184 if (m_status == BZ_IO_ERROR)
185 throw std::runtime_error ("failed to write or compress");
186 }
187 if (std::ferror (m_source.m_fp))
188 throw std::runtime_error ("failed to read from source file");
189 }
190
191 void close (void)
192 {
193 int abandon = (m_status == BZ_IO_ERROR) ? 1 : 0;
194 BZ2_bzWriteClose (&m_status, m_bz, abandon, nullptr, nullptr);
195 if (m_status != BZ_OK)
196 throw std::runtime_error ("failed to close bzip2 stream");
197 m_bz = nullptr;
198
199 // We have no error handling for failing to close source, let
200 // the destructor close it.
201 m_dest.close ();
202 }
203
204 private:
205
206 int m_status;
207 CFile m_source;
208 CFile m_dest;
209 BZFILE *m_bz;
210 };
211 };
212
213#endif
214
215 // Note about zlib and gzip
216 //
217 // gzip is a format for compressed single files. zlib is a format
218 // designed for in-memory and communication channel applications.
219 // gzip uses the same format internally for the compressed data but
220 // has different headers and trailers.
221 //
222 // zlib is also a library but gzip is not. Very old versions of zlib do
223 // not include functions to create useful gzip headers and trailers:
224 //
225 // Note that you cannot specify special gzip header contents (e.g.
226 // a file name or modification date), nor will inflate tell you what
227 // was in the gzip header. If you need to customize the header or
228 // see what's in it, you can use the raw deflate and inflate
229 // operations and the crc32() function and roll your own gzip
230 // encoding and decoding. Read the gzip RFC 1952 for details of the
231 // header and trailer format.
232 // zlib FAQ
233 //
234 // Recent versions (on which we are already dependent) have deflateInit2()
235 // to do it. We still need to get the right metadata for the header
236 // ourselves though.
237 //
238 // The header is defined in RFC #1952
239 // GZIP file format specification version 4.3
240
241
242#if defined (HAVE_Z)
243
244 class gz
245 {
246 public:
247
248 static const constexpr char *extension = ".gz";
249
250 static void zip (const std::string& source_path,
251 const std::string& dest_path)
252 {
253 gz::zipper z (source_path, dest_path);
254 z.deflate ();
255 z.close ();
256 }
257
258 private:
259
260 // Util class to get a non-const char*
261 class uchar_array
262 {
263 public:
264
265 // Bytef is a typedef for unsigned char
266 unsigned char *p;
267
268 uchar_array (void) = delete;
269
270 uchar_array (const std::string& str)
271 {
272 p = new Bytef[str.length () + 1];
273 std::strcpy (reinterpret_cast<char *> (p), str.c_str ());
274 }
275
276 uchar_array (const uchar_array&) = delete;
277
278 uchar_array& operator = (const uchar_array&) = delete;
279
280 ~uchar_array (void) { delete[] p; }
281 };
282
283 class gzip_header : public gz_header
284 {
285 public:
286
287 gzip_header (void) = delete;
288
289 gzip_header (const std::string& source_path)
290 : m_basename (sys::env::base_pathname (source_path))
291 {
292 const sys::file_stat source_stat (source_path);
293 if (! source_stat)
294 throw std::runtime_error ("unable to stat source file");
295
296 // time_t may be a signed int in which case it will be a
297 // positive number so it is safe to uLong. Or is it? Can
298 // unix_time really never be negative?
299 time = uLong (source_stat.mtime ().unix_time ());
300
301 // If FNAME is set, an original file name is present,
302 // terminated by a zero byte. The name must consist of ISO
303 // 8859-1 (LATIN-1) characters; on operating systems using
304 // EBCDIC or any other character set for file names, the name
305 // must be translated to the ISO LATIN-1 character set. This
306 // is the original name of the file being compressed, with any
307 // directory components removed, and, if the file being
308 // compressed is on a file system with case insensitive names,
309 // forced to lower case.
310 name = m_basename.p;
311
312 // If we don't set it to Z_NULL, then it will set FCOMMENT (4th bit)
313 // on the FLG byte, and then write {0, 3} comment.
314 comment = Z_NULL;
315
316 // Seems to already be the default but we are not taking chances.
317 extra = Z_NULL;
318
319 // We do not want a CRC for the header. That would be only 2 more
320 // bytes, and maybe it would be a good thing but we want to generate
321 // gz files similar to the default gzip application.
322 hcrc = 0;
323
324 // OS (Operating System):
325 // 0 - FAT filesystem (MS-DOS, OS/2, NT/Win32)
326 // 1 - Amiga
327 // 2 - VMS (or OpenVMS)
328 // 3 - Unix
329 // 4 - VM/CMS
330 // 5 - Atari TOS
331 // 6 - HPFS filesystem (OS/2, NT)
332 // 7 - Macintosh
333 // 8 - Z-System
334 // 9 - CP/M
335 // 10 - TOPS-20
336 // 11 - NTFS filesystem (NT)
337 // 12 - QDOS
338 // 13 - Acorn RISCOS
339 // 255 - unknown
340 //
341 // The list is problematic because it mixes OS and filesystem. It
342 // also does not specify whether filesystem relates to source or
343 // destination file.
344
345#if defined (__WIN32__)
346 // Or should it be 11?
347 os = 0;
348#elif defined (__APPLE__)
349 os = 7;
350#else
351 // Unix by default?
352 os = 3;
353#endif
354 }
355
356 gzip_header (const gzip_header&) = delete;
357
358 gzip_header& operator = (const gzip_header&) = delete;
359
360 ~gzip_header (void) = default;
361
362 private:
363
364 // This must be kept for gz_header.name
365 uchar_array m_basename;
366 };
367
368 class zipper
369 {
370 public:
371
372 zipper (void) = delete;
373
374 zipper (const std::string& source_path, const std::string& dest_path)
375 : m_source (source_path, "rb"), m_dest (dest_path, "wb"),
376 m_header (source_path), m_strm (new z_stream)
377 {
378 m_strm->zalloc = Z_NULL;
379 m_strm->zfree = Z_NULL;
380 m_strm->opaque = Z_NULL;
381 }
382
383 zipper (const zipper&) = delete;
384
385 zipper& operator = (const zipper&) = delete;
386
387 ~zipper (void)
388 {
389 if (m_strm)
390 deflateEnd (m_strm);
391 delete m_strm;
392 }
393
394 void deflate (void)
395 {
396 // int deflateInit2 (z_streamp m_strm,
397 // int level, // compression level (default is 8)
398 // int method,
399 // int windowBits, // 15 (default) + 16 (gzip format)
400 // int memLevel, // memory usage (default is 8)
401 // int strategy);
402 int status = deflateInit2 (m_strm, 8, Z_DEFLATED, 31, 8,
403 Z_DEFAULT_STRATEGY);
404 if (status != Z_OK)
405 throw std::runtime_error ("failed to open zlib stream");
406
407 deflateSetHeader (m_strm, &m_header);
408
409 const std::size_t buf_len = 8192;
410 unsigned char buf_in[buf_len];
411 unsigned char buf_out[buf_len];
412
413 int flush;
414
415 do
416 {
417 m_strm->avail_in = std::fread (buf_in, sizeof (buf_in[0]),
418 buf_len, m_source.m_fp);
419
420 if (std::ferror (m_source.m_fp))
421 throw std::runtime_error ("failed to read source file");
422
423 m_strm->next_in = buf_in;
424 flush = (std::feof (m_source.m_fp) ? Z_FINISH : Z_NO_FLUSH);
425
426 // If deflate returns Z_OK and with zero avail_out, it must be
427 // called again after making room in the output buffer because
428 // there might be more output pending.
429 do
430 {
431 m_strm->avail_out = buf_len;
432 m_strm->next_out = buf_out;
433 status = ::deflate (m_strm, flush);
434 if (status == Z_STREAM_ERROR)
435 throw std::runtime_error ("failed to deflate");
436
437 std::fwrite (buf_out, sizeof (buf_out[0]),
438 buf_len - m_strm->avail_out, m_dest.m_fp);
439 if (std::ferror (m_dest.m_fp))
440 throw std::runtime_error ("failed to write file");
441 }
442 while (m_strm->avail_out == 0);
443
444 if (m_strm->avail_in != 0)
445 throw std::runtime_error ("failed to write file");
446
447 } while (flush != Z_FINISH);
448
449 if (status != Z_STREAM_END)
450 throw std::runtime_error ("failed to write file");
451 }
452
453 void close (void)
454 {
455 if (deflateEnd (m_strm) != Z_OK)
456 throw std::runtime_error ("failed to close zlib stream");
457 m_strm = nullptr;
458
459 // We have no error handling for failing to close source, let
460 // the destructor close it.
461 m_dest.close ();
462 }
463
464 private:
465
466 CFile m_source;
467 CFile m_dest;
468 gzip_header m_header;
469 z_stream *m_strm;
470 };
471 };
472
473#endif
474
475
476 template<typename X>
478 xzip (const Array<std::string>& source_patterns,
479 const std::function<std::string(const std::string&)>& mk_dest_path)
480 {
481 std::list<std::string> dest_paths;
482
483 std::function<void(const std::string&)> walk;
484 walk = [&walk, &mk_dest_path, &dest_paths] (const std::string& path) -> void
485 {
486 const sys::file_stat fs (path);
487 // is_dir and is_reg will return false if failed to stat.
488 if (fs.is_dir ())
489 {
490 string_vector dirlist;
491 std::string msg;
492
493 // Collect the whole list of filenames first, before recursion
494 // to avoid issues with infinite loop if the action generates
495 // files in the same directory (highly likely).
496 if (sys::get_dirlist (path, dirlist, msg))
497 {
498 for (octave_idx_type i = 0; i < dirlist.numel (); i++)
499 if (dirlist(i) != "." && dirlist(i) != "..")
500 walk (sys::file_ops::concat (path, dirlist(i)));
501 }
502 // Note that we skip any problem with directories.
503 }
504 else if (fs.is_reg ())
505 {
506 const std::string dest_path = mk_dest_path (path);
507 try
508 {
509 X::zip (path, dest_path);
510 }
511 catch (const interrupt_exception&)
512 {
513 throw; // interrupts are special, just re-throw.
514 }
515 catch (...)
516 {
517 // Error "handling" is not including filename on the output list.
518 // Also, remove created file which may not have been created
519 // in the first place. Note that it is possible for the file
520 // to exist before the call to X::zip and that X::zip has not
521 // clobber it yet, but we remove it anyway.
522 sys::unlink (dest_path);
523 return;
524 }
525 dest_paths.push_front (dest_path);
526 }
527 // Skip all other file types and errors.
528 return;
529 };
530
531 for (octave_idx_type i = 0; i < source_patterns.numel (); i++)
532 {
533 const glob_match pattern (sys::file_ops::tilde_expand (source_patterns(i)));
534 const string_vector filepaths = pattern.glob ();
535 for (octave_idx_type j = 0; j < filepaths.numel (); j++)
536 walk (filepaths(j));
537 }
538 return string_vector (dest_paths);
539 }
540
541
542 template<typename X>
544 xzip (const Array<std::string>& source_patterns)
545 {
546 const std::string ext = X::extension;
547 const std::function<std::string(const std::string&)> mk_dest_path
548 = [&ext] (const std::string& source_path) -> std::string
549 {
550 return source_path + ext;
551 };
552 return xzip<X> (source_patterns, mk_dest_path);
553 }
554
555 template<typename X>
557 xzip (const Array<std::string>& source_patterns, const std::string& out_dir)
558 {
559 const std::string ext = X::extension;
560 const std::function<std::string(const std::string&)> mk_dest_path
561 = [&out_dir, &ext] (const std::string& source_path) -> std::string
562 {
563 // Strip any relative path (bug #58547)
564 std::size_t pos = source_path.find_last_of (sys::file_ops::dir_sep_str ());
565 const std::string basename =
566 (pos == std::string::npos ? source_path : source_path.substr (pos+1));
567 return sys::file_ops::concat (out_dir, basename + ext);
568 };
569
570 // We don't care if mkdir fails. Maybe it failed because it already
571 // exists, or maybe it can't be created. If the first, then there's
572 // nothing to do, if the later, then it will be handled later. Any
573 // is to be handled by not listing files in the output.
574 sys::mkdir (out_dir, 0777);
575 return xzip<X> (source_patterns, mk_dest_path);
576 }
577
578 template<typename X>
579 static octave_value_list
580 xzip (const std::string& func_name, const octave_value_list& args)
581 {
582 const octave_idx_type nargin = args.length ();
583 if (nargin < 1 || nargin > 2)
584 print_usage ();
585
586 const Array<std::string> source_patterns
587 = args(0).xcellstr_value ("%s: FILES must be a character array or cellstr",
588 func_name.c_str ());
589 if (nargin == 1)
590 return octave_value (Cell (xzip<X> (source_patterns)));
591 else // nargin == 2
592 {
593 const std::string out_dir = args(1).string_value ();
594 return octave_value (Cell (xzip<X> (source_patterns, out_dir)));
595 }
596 }
597
598DEFUN_DLD (gzip, args, nargout,
599 doc: /* -*- texinfo -*-
600@deftypefn {} {@var{filelist} =} gzip (@var{files})
601@deftypefnx {} {@var{filelist} =} gzip (@var{files}, @var{dir})
602Compress the list of files and directories specified in @var{files}.
603
604@var{files} is a character array or cell array of strings. Shell wildcards
605in the filename such as @samp{*} or @samp{?} are accepted and expanded.
606Each file is compressed separately and a new file with a @file{".gz"}
607extension is created. The original files are not modified, but existing
608compressed files will be silently overwritten. If a directory is
609specified then @code{gzip} recursively compresses all files in the
610directory.
611
612If @var{dir} is defined the compressed files are placed in this directory,
613rather than the original directory where the uncompressed file resides.
614Note that this does not replicate a directory tree in @var{dir} which may
615lead to files overwriting each other if there are multiple files with the
616same name.
617
618If @var{dir} does not exist it is created.
619
620The optional output @var{filelist} is a list of the compressed files.
621@seealso{gunzip, unpack, bzip2, zip, tar}
622@end deftypefn */)
623{
624#if defined (HAVE_Z)
625
626 octave_value_list retval = xzip<gz> ("gzip", args);
627
628 return (nargout > 0 ? retval : octave_value_list ());
629
630#else
631
632 octave_unused_parameter (args);
633 octave_unused_parameter (nargout);
634
635 err_disabled_feature ("gzip", "gzip");
636
637#endif
638}
639
640/*
641%!error gzip ()
642%!error gzip ("1", "2", "3")
643%!error <FILES must be a character array or cellstr|was unavailable or disabled> gzip (1)
644*/
645
646DEFUN_DLD (bzip2, args, nargout,
647 doc: /* -*- texinfo -*-
648@deftypefn {} {@var{filelist} =} bzip2 (@var{files})
649@deftypefnx {} {@var{filelist} =} bzip2 (@var{files}, @var{dir})
650Compress the list of files specified in @var{files}.
651
652@var{files} is a character array or cell array of strings. Shell wildcards
653in the filename such as @samp{*} or @samp{?} are accepted and expanded.
654Each file is compressed separately and a new file with a @file{".bz2"}
655extension is created. The original files are not modified, but existing
656compressed files will be silently overwritten.
657
658If @var{dir} is defined the compressed files are placed in this directory,
659rather than the original directory where the uncompressed file resides.
660Note that this does not replicate a directory tree in @var{dir} which may
661lead to files overwriting each other if there are multiple files with the
662same name.
663
664If @var{dir} does not exist it is created.
665
666The optional output @var{filelist} is a list of the compressed files.
667@seealso{bunzip2, unpack, gzip, zip, tar}
668@end deftypefn */)
669{
670#if defined (HAVE_BZ2)
671
672 octave_value_list retval = xzip<bz2> ("bzip2", args);
673
674 return (nargout > 0 ? retval : octave_value_list ());
675
676#else
677
678 octave_unused_parameter (args);
679 octave_unused_parameter (nargout);
680
681 err_disabled_feature ("bzip2", "bzip2");
682
683#endif
684}
685
686// Tests for both gzip/bzip2 and gunzip/bunzip2
687/*
688
689## Takes a single argument, a function handle for the test. This other
690## function must accept two arguments, a directory for the tests, and
691## a cell array with zip function, unzip function, and file extension.
692
693%!function run_test_function (test_function)
694%! enabled_zippers = struct ("zip", {}, "unzip", {}, "ext", {});
695%! if (__octave_config_info__ ().build_features.BZ2)
696%! enabled_zippers(end+1).zip = @bzip2;
697%! enabled_zippers(end).unzip = @bunzip2;
698%! enabled_zippers(end).ext = ".bz2";
699%! endif
700%! if (__octave_config_info__ ().build_features.Z)
701%! enabled_zippers(end+1).zip = @gzip;
702%! enabled_zippers(end).unzip = @gunzip;
703%! enabled_zippers(end).ext = ".gz";
704%! endif
705%!
706%! for z = enabled_zippers
707%! test_dir = tempname ();
708%! if (! mkdir (test_dir))
709%! error ("unable to create directory for tests");
710%! endif
711%! unwind_protect
712%! test_function (test_dir, z)
713%! unwind_protect_cleanup
714%! confirm_recursive_rmdir (false, "local");
715%! sts = rmdir (test_dir, "s");
716%! end_unwind_protect
717%! endfor
718%!endfunction
719
720%!function create_file (fpath, data)
721%! fid = fopen (fpath, "wb");
722%! if (fid < 0)
723%! error ("unable to open file for writing");
724%! endif
725%! if (fwrite (fid, data, class (data)) != numel (data))
726%! error ("unable to write to file");
727%! endif
728%! if (fflush (fid) || fclose (fid))
729%! error ("unable to flush or close file");
730%! endif
731%!endfunction
732
733%!function unlink_or_error (filepath)
734%! [err, msg] = unlink (filepath);
735%! if (err)
736%! error ("unable to remove file required for the test");
737%! endif
738%!endfunction
739
740## Test with large files because of varied buffer size
741%!function test_large_file (test_dir, z)
742%! test_file = tempname (test_dir);
743%! create_file (test_file, rand (500000, 1));
744%! md5 = hash ("md5", fileread (test_file));
745%!
746%! z_file = [test_file z.ext];
747%! z_filelist = z.zip (test_file);
748%! assert (is_same_file (z_filelist, {z_file}))
749%!
750%! unlink_or_error (test_file);
751%! uz_filelist = z.unzip (z_file);
752%! assert (is_same_file (uz_filelist, {test_file}))
753%!
754%! assert (hash ("md5", fileread (test_file)), md5)
755%!endfunction
756%!test run_test_function (@test_large_file)
757
758## Test that xzipped files are rexzipped (hits bug #43206, #48598)
759%!function test_z_z (test_dir, z)
760%! ori_file = tempname (test_dir);
761%! create_file (ori_file, rand (100, 1));
762%! md5_ori = hash ("md5", fileread (ori_file));
763%!
764%! z_file = [ori_file z.ext];
765%! z_filelist = z.zip (ori_file);
766%! assert (is_same_file (z_filelist, {z_file})) # check output
767%! assert (exist (z_file), 2) # confirm file exists
768%! assert (exist (ori_file), 2) # and did not remove original file
769%!
770%! unlink_or_error (ori_file);
771%! uz_filelist = z.unzip (z_file);
772%! assert (is_same_file (uz_filelist, {ori_file})) # bug #48598
773%! assert (hash ("md5", fileread (ori_file)), md5_ori)
774%! assert (exist (z_file), 2) # bug #48597
775%!
776%! ## xzip should preserve original files.
777%! z_z_file = [z_file z.ext];
778%! z_z_filelist = z.zip (z_file);
779%! assert (is_same_file (z_z_filelist, {z_z_file})) # check output
780%! assert (exist (z_z_file), 2) # confirm file exists
781%! assert (exist (z_file), 2)
782%!
783%! md5_z = hash ("md5", fileread (z_file));
784%! unlink_or_error (z_file);
785%! uz_z_filelist = z.unzip (z_z_file);
786%! assert (is_same_file (uz_z_filelist, {z_file})) # bug #48598
787%! assert (exist (z_z_file), 2) # bug #43206
788%! assert (hash ("md5", fileread (z_file)), md5_z)
789%!endfunction
790%!test <43206> run_test_function (@test_z_z)
791
792%!function test_xzip_dir (test_dir, z) # bug #43431
793%! fpaths = fullfile (test_dir, {"test1", "test2", "test3"});
794%! md5s = cell (1, 3);
795%! for idx = 1:numel (fpaths)
796%! create_file (fpaths{idx}, rand (100, 1));
797%! md5s(idx) = hash ("md5", fileread (fpaths{idx}));
798%! endfor
799%!
800%! test_dir = [test_dir filesep()];
801%!
802%! z_files = strcat (fpaths, z.ext);
803%! z_filelist = z.zip (test_dir);
804%! assert (sort (z_filelist), z_files(:))
805%! for idx = 1:numel (fpaths)
806%! assert (exist (z_files{idx}), 2)
807%! unlink_or_error (fpaths{idx});
808%! endfor
809%!
810%! ## only gunzip handles directory (bunzip2 should too though)
811%! if (z.unzip == @gunzip)
812%! uz_filelist = z.unzip (test_dir);
813%! else
814%! uz_filelist = cell (1, numel (z_filelist));
815%! for idx = 1:numel (z_filelist)
816%! uz_filelist(idx) = z.unzip (z_filelist{idx});
817%! endfor
818%! endif
819%! uz_filelist = sort (uz_filelist);
820%! fpaths = sort (fpaths);
821%! assert (is_same_file (uz_filelist(:), fpaths(:))) # bug #48598
822%! for idx = 1:numel (fpaths)
823%! assert (hash ("md5", fileread (fpaths{idx})), md5s{idx})
824%! endfor
825%!endfunction
826%!test <48598> run_test_function (@test_xzip_dir)
827
828%!function test_save_to_dir (test_dir, z)
829%! filename = "test-file";
830%! filepath = fullfile (test_dir, filename);
831%! create_file (filepath, rand (100, 1));
832%! md5 = hash ("md5", fileread (filepath));
833%!
834%! ## test with existing and non-existing directory
835%! out_dirs = {tempname (test_dir), tempname (test_dir)};
836%! if (! mkdir (out_dirs{1}))
837%! error ("unable to create directory for test");
838%! endif
839%! unwind_protect
840%! for idx = 1:numel (out_dirs)
841%! out_dir = out_dirs{idx};
842%! uz_file = fullfile (out_dir, filename);
843%! z_file = [uz_file z.ext];
844%!
845%! z_filelist = z.zip (filepath, out_dir);
846%! assert (z_filelist, {z_file})
847%! assert (exist (z_file, "file"), 2)
848%!
849%! uz_filelist = z.unzip (z_file);
850%! assert (is_same_file (uz_filelist, {uz_file})) # bug #48598
851%!
852%! assert (hash ("md5", fileread (uz_file)), md5)
853%! endfor
854%! unwind_protect_cleanup
855%! confirm_recursive_rmdir (false, "local");
856%! for idx = 1:numel (out_dirs)
857%! sts = rmdir (out_dirs{idx}, "s");
858%! endfor
859%! end_unwind_protect
860%!endfunction
861%!test run_test_function (@test_save_to_dir)
862*/
863
864OCTAVE_NAMESPACE_END
ComplexNDArray concat(NDArray &ra, ComplexNDArray &rb, const Array< octave_idx_type > &ra_idx)
Definition: CNDArray.cc:418
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:411
RIIA wrapper for std::FILE*.
Definition: gzip.cc:98
CFile(const std::string &path, const std::string &mode)
Definition: gzip.cc:103
CFile(const CFile &)=delete
void close(void)
Definition: gzip.cc:120
CFile & operator=(const CFile &)=delete
CFile(void)=delete
std::FILE * m_fp
Definition: gzip.cc:128
~CFile(void)
Definition: gzip.cc:114
Definition: Cell.h:43
string_vector glob(void) const
Definition: glob-match.cc:41
octave_idx_type length(void) const
Definition: ovl.h:113
octave_idx_type numel(void) const
Definition: str-vec.h:100
#define DEFUN_DLD(name, args_name, nargout_name, doc)
Macro to define an at run time dynamically loadable builtin function.
Definition: defun-dld.h:61
OCTINTERP_API void print_usage(void)
Definition: defun-int.h:72
void err_disabled_feature(const std::string &fcn, const std::string &feature, const std::string &pkg)
Definition: errwarn.cc:53
QString path
QString name
string_vector xzip(const Array< std::string > &source_patterns, const std::function< std::string(const std::string &)> &mk_dest_path)
Definition: gzip.cc:478
static std::string basename(const std::string &s, bool strip_path=false)
std::string tilde_expand(const std::string &name)
Definition: file-ops.cc:281
std::string dir_sep_str(void)
Definition: file-ops.cc:238
int mkdir(const std::string &nm, mode_t md)
Definition: file-ops.cc:399
int unlink(const std::string &name)
Definition: file-ops.cc:621
bool get_dirlist(const std::string &dirname, string_vector &dirlist, std::string &msg)
Definition: lo-sysdep.cc:119
std::FILE * fopen(const std::string &filename, const std::string &mode)
Definition: lo-sysdep.cc:314
return octave_value(v1.char_array_value() . concat(v2.char_array_value(), ra_idx),((a1.is_sq_string()||a2.is_sq_string()) ? '\'' :'"'))