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