GNU Octave  8.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-2023 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 (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  }
448  while (flush != Z_FINISH);
449 
450  if (status != Z_STREAM_END)
451  throw std::runtime_error ("failed to write file");
452  }
453 
454  void close (void)
455  {
456  if (deflateEnd (m_strm) != Z_OK)
457  throw std::runtime_error ("failed to close zlib stream");
458  m_strm = nullptr;
459 
460  // We have no error handling for failing to close source, let
461  // the destructor close it.
462  m_dest.close ();
463  }
464 
465  private:
466 
467  CFile m_source;
468  CFile m_dest;
469  gzip_header m_header;
470  z_stream *m_strm;
471  };
472 };
473 
474 #endif
475 
476 
477 template<typename X>
479 xzip (const Array<std::string>& source_patterns,
480  const std::function<std::string(const std::string&)>& mk_dest_path)
481 {
482  std::list<std::string> dest_paths;
483 
484  std::function<void(const std::string&)> walk;
485  walk = [&walk, &mk_dest_path, &dest_paths] (const std::string& path) -> void
486  {
487  const sys::file_stat fs (path);
488  // is_dir and is_reg will return false if failed to stat.
489  if (fs.is_dir ())
490  {
491  string_vector dirlist;
492  std::string msg;
493 
494  // Collect the whole list of filenames first, before recursion
495  // to avoid issues with infinite loop if the action generates
496  // files in the same directory (highly likely).
497  if (sys::get_dirlist (path, dirlist, msg))
498  {
499  for (octave_idx_type i = 0; i < dirlist.numel (); i++)
500  if (dirlist(i) != "." && dirlist(i) != "..")
501  walk (sys::file_ops::concat (path, dirlist(i)));
502  }
503  // Note that we skip any problem with directories.
504  }
505  else if (fs.is_reg ())
506  {
507  const std::string dest_path = mk_dest_path (path);
508  try
509  {
510  X::zip (path, dest_path);
511  }
512  catch (const interrupt_exception&)
513  {
514  throw; // interrupts are special, just re-throw.
515  }
516  catch (...)
517  {
518  // Error "handling" is not including filename on the output list.
519  // Also, remove created file which may not have been created
520  // in the first place. Note that it is possible for the file
521  // to exist before the call to X::zip and that X::zip has not
522  // clobber it yet, but we remove it anyway.
523  sys::unlink (dest_path);
524  return;
525  }
526  dest_paths.push_front (dest_path);
527  }
528  // Skip all other file types and errors.
529  return;
530  };
531 
532  for (octave_idx_type i = 0; i < source_patterns.numel (); i++)
533  {
534  const glob_match pattern (sys::file_ops::tilde_expand (source_patterns(i)));
535  const string_vector filepaths = pattern.glob ();
536  for (octave_idx_type j = 0; j < filepaths.numel (); j++)
537  walk (filepaths(j));
538  }
539  return string_vector (dest_paths);
540 }
541 
542 
543 template<typename X>
545 xzip (const Array<std::string>& source_patterns)
546 {
547  const std::string ext = X::extension;
548  const std::function<std::string(const std::string&)> mk_dest_path
549  = [&ext] (const std::string& source_path) -> std::string
550  {
551  return source_path + ext;
552  };
553  return xzip<X> (source_patterns, mk_dest_path);
554 }
555 
556 template<typename X>
558 xzip (const Array<std::string>& source_patterns, const std::string& out_dir)
559 {
560  const std::string ext = X::extension;
561  const std::function<std::string(const std::string&)> mk_dest_path
562  = [&out_dir, &ext] (const std::string& source_path) -> std::string
563  {
564  // Strip any relative path (bug #58547)
565  std::size_t pos = source_path.find_last_of (sys::file_ops::dir_sep_str ());
566  const std::string basename =
567  (pos == std::string::npos ? source_path : source_path.substr (pos+1));
568  return sys::file_ops::concat (out_dir, basename + ext);
569  };
570 
571  // We don't care if mkdir fails. Maybe it failed because it already
572  // exists, or maybe it can't be created. If the first, then there's
573  // nothing to do, if the later, then it will be handled later. Any
574  // is to be handled by not listing files in the output.
575  sys::mkdir (out_dir, 0777);
576  return xzip<X> (source_patterns, mk_dest_path);
577 }
578 
579 template<typename X>
580 static octave_value_list
581 xzip (const std::string& fcn_name, const octave_value_list& args)
582 {
583  const octave_idx_type nargin = args.length ();
584  if (nargin < 1 || nargin > 2)
585  print_usage ();
586 
587  const Array<std::string> source_patterns
588  = args(0).xcellstr_value ("%s: FILES must be a character array or cellstr",
589  fcn_name.c_str ());
590  if (nargin == 1)
591  return octave_value (Cell (xzip<X> (source_patterns)));
592  else // nargin == 2
593  {
594  const std::string out_dir = args(1).string_value ();
595  return octave_value (Cell (xzip<X> (source_patterns, out_dir)));
596  }
597 }
598 
599 DEFUN_DLD (gzip, args, nargout,
600  doc: /* -*- texinfo -*-
601 @deftypefn {} {@var{filelist} =} gzip (@var{files})
602 @deftypefnx {} {@var{filelist} =} gzip (@var{files}, @var{dir})
603 Compress the list of files and directories specified in @var{files}.
604 
605 @var{files} is a character array or cell array of strings. Shell wildcards
606 in the filename such as @samp{*} or @samp{?} are accepted and expanded.
607 Each file is compressed separately and a new file with a @file{".gz"}
608 extension is created. The original files are not modified, but existing
609 compressed files will be silently overwritten. If a directory is
610 specified then @code{gzip} recursively compresses all files in the
611 directory.
612 
613 If @var{dir} is defined the compressed files are placed in this directory,
614 rather than the original directory where the uncompressed file resides.
615 Note that this does not replicate a directory tree in @var{dir} which may
616 lead to files overwriting each other if there are multiple files with the
617 same name.
618 
619 If @var{dir} does not exist it is created.
620 
621 The optional output @var{filelist} is a list of the compressed files.
622 @seealso{gunzip, unpack, bzip2, zip, tar}
623 @end deftypefn */)
624 {
625 #if defined (HAVE_Z)
626 
627  octave_value_list retval = xzip<gz> ("gzip", args);
628 
629  return (nargout > 0 ? retval : octave_value_list ());
630 
631 #else
632 
633  octave_unused_parameter (args);
634  octave_unused_parameter (nargout);
635 
636  err_disabled_feature ("gzip", "gzip");
637 
638 #endif
639 }
640 
641 /*
642 %!error gzip ()
643 %!error gzip ("1", "2", "3")
644 %!error <FILES must be a character array or cellstr|was unavailable or disabled> gzip (1)
645 */
646 
647 DEFUN_DLD (bzip2, args, nargout,
648  doc: /* -*- texinfo -*-
649 @deftypefn {} {@var{filelist} =} bzip2 (@var{files})
650 @deftypefnx {} {@var{filelist} =} bzip2 (@var{files}, @var{dir})
651 Compress the list of files specified in @var{files}.
652 
653 @var{files} is a character array or cell array of strings. Shell wildcards
654 in the filename such as @samp{*} or @samp{?} are accepted and expanded.
655 Each file is compressed separately and a new file with a @file{".bz2"}
656 extension is created. The original files are not modified, but existing
657 compressed files will be silently overwritten.
658 
659 If @var{dir} is defined the compressed files are placed in this directory,
660 rather than the original directory where the uncompressed file resides.
661 Note that this does not replicate a directory tree in @var{dir} which may
662 lead to files overwriting each other if there are multiple files with the
663 same name.
664 
665 If @var{dir} does not exist it is created.
666 
667 The optional output @var{filelist} is a list of the compressed files.
668 @seealso{bunzip2, unpack, gzip, zip, tar}
669 @end deftypefn */)
670 {
671 #if defined (HAVE_BZ2)
672 
673  octave_value_list retval = xzip<bz2> ("bzip2", args);
674 
675  return (nargout > 0 ? retval : octave_value_list ());
676 
677 #else
678 
679  octave_unused_parameter (args);
680  octave_unused_parameter (nargout);
681 
682  err_disabled_feature ("bzip2", "bzip2");
683 
684 #endif
685 }
686 
687 // Tests for both gzip/bzip2 and gunzip/bunzip2
688 /*
689 
690 ## Takes a single argument, a function handle for the test. This other
691 ## function must accept two arguments, a directory for the tests, and
692 ## a cell array with zip function, unzip function, and file extension.
693 
694 %!function run_test_function (test_function)
695 %! enabled_zippers = struct ("zip", {}, "unzip", {}, "ext", {});
696 %! if (__octave_config_info__ ().build_features.BZ2)
697 %! enabled_zippers(end+1).zip = @bzip2;
698 %! enabled_zippers(end).unzip = @bunzip2;
699 %! enabled_zippers(end).ext = ".bz2";
700 %! endif
701 %! if (__octave_config_info__ ().build_features.Z)
702 %! enabled_zippers(end+1).zip = @gzip;
703 %! enabled_zippers(end).unzip = @gunzip;
704 %! enabled_zippers(end).ext = ".gz";
705 %! endif
706 %!
707 %! for z = enabled_zippers
708 %! test_dir = tempname ();
709 %! if (! mkdir (test_dir))
710 %! error ("unable to create directory for tests");
711 %! endif
712 %! unwind_protect
713 %! test_function (test_dir, z)
714 %! unwind_protect_cleanup
715 %! confirm_recursive_rmdir (false, "local");
716 %! sts = rmdir (test_dir, "s");
717 %! end_unwind_protect
718 %! endfor
719 %!endfunction
720 
721 %!function create_file (fpath, data)
722 %! fid = fopen (fpath, "wb");
723 %! if (fid < 0)
724 %! error ("unable to open file for writing");
725 %! endif
726 %! if (fwrite (fid, data, class (data)) != numel (data))
727 %! error ("unable to write to file");
728 %! endif
729 %! if (fflush (fid) || fclose (fid))
730 %! error ("unable to flush or close file");
731 %! endif
732 %!endfunction
733 
734 %!function unlink_or_error (filepath)
735 %! [err, msg] = unlink (filepath);
736 %! if (err)
737 %! error ("unable to remove file required for the test");
738 %! endif
739 %!endfunction
740 
741 ## Test with large files because of varied buffer size
742 %!function test_large_file (test_dir, z)
743 %! test_file = tempname (test_dir);
744 %! create_file (test_file, rand (500000, 1));
745 %! md5 = hash ("md5", fileread (test_file));
746 %!
747 %! z_file = [test_file z.ext];
748 %! z_filelist = z.zip (test_file);
749 %! assert (is_same_file (z_filelist, {z_file}))
750 %!
751 %! unlink_or_error (test_file);
752 %! uz_filelist = z.unzip (z_file);
753 %! assert (is_same_file (uz_filelist, {test_file}))
754 %!
755 %! assert (hash ("md5", fileread (test_file)), md5)
756 %!endfunction
757 %!test run_test_function (@test_large_file)
758 
759 ## Test that xzipped files are rexzipped (hits bug #43206, #48598)
760 %!function test_z_z (test_dir, z)
761 %! ori_file = tempname (test_dir);
762 %! create_file (ori_file, rand (100, 1));
763 %! md5_ori = hash ("md5", fileread (ori_file));
764 %!
765 %! z_file = [ori_file z.ext];
766 %! z_filelist = z.zip (ori_file);
767 %! assert (is_same_file (z_filelist, {z_file})) # check output
768 %! assert (exist (z_file), 2) # confirm file exists
769 %! assert (exist (ori_file), 2) # and did not remove original file
770 %!
771 %! unlink_or_error (ori_file);
772 %! uz_filelist = z.unzip (z_file);
773 %! assert (is_same_file (uz_filelist, {ori_file})) # bug #48598
774 %! assert (hash ("md5", fileread (ori_file)), md5_ori)
775 %! assert (exist (z_file), 2) # bug #48597
776 %!
777 %! ## xzip should preserve original files.
778 %! z_z_file = [z_file z.ext];
779 %! z_z_filelist = z.zip (z_file);
780 %! assert (is_same_file (z_z_filelist, {z_z_file})) # check output
781 %! assert (exist (z_z_file), 2) # confirm file exists
782 %! assert (exist (z_file), 2)
783 %!
784 %! md5_z = hash ("md5", fileread (z_file));
785 %! unlink_or_error (z_file);
786 %! uz_z_filelist = z.unzip (z_z_file);
787 %! assert (is_same_file (uz_z_filelist, {z_file})) # bug #48598
788 %! assert (exist (z_z_file), 2) # bug #43206
789 %! assert (hash ("md5", fileread (z_file)), md5_z)
790 %!endfunction
791 %!test <43206> run_test_function (@test_z_z)
792 
793 %!function test_xzip_dir (test_dir, z) # bug #43431
794 %! fpaths = fullfile (test_dir, {"test1", "test2", "test3"});
795 %! md5s = cell (1, 3);
796 %! for idx = 1:numel (fpaths)
797 %! create_file (fpaths{idx}, rand (100, 1));
798 %! md5s(idx) = hash ("md5", fileread (fpaths{idx}));
799 %! endfor
800 %!
801 %! test_dir = [test_dir filesep()];
802 %!
803 %! z_files = strcat (fpaths, z.ext);
804 %! z_filelist = z.zip (test_dir);
805 %! assert (sort (z_filelist), z_files(:))
806 %! for idx = 1:numel (fpaths)
807 %! assert (exist (z_files{idx}), 2)
808 %! unlink_or_error (fpaths{idx});
809 %! endfor
810 %!
811 %! ## only gunzip handles directory (bunzip2 should too though)
812 %! if (z.unzip == @gunzip)
813 %! uz_filelist = z.unzip (test_dir);
814 %! else
815 %! uz_filelist = cell (1, numel (z_filelist));
816 %! for idx = 1:numel (z_filelist)
817 %! uz_filelist(idx) = z.unzip (z_filelist{idx});
818 %! endfor
819 %! endif
820 %! uz_filelist = sort (uz_filelist);
821 %! fpaths = sort (fpaths);
822 %! assert (is_same_file (uz_filelist(:), fpaths(:))) # bug #48598
823 %! for idx = 1:numel (fpaths)
824 %! assert (hash ("md5", fileread (fpaths{idx})), md5s{idx})
825 %! endfor
826 %!endfunction
827 %!test <48598> run_test_function (@test_xzip_dir)
828 
829 %!function test_save_to_dir (test_dir, z)
830 %! filename = "test-file";
831 %! filepath = fullfile (test_dir, filename);
832 %! create_file (filepath, rand (100, 1));
833 %! md5 = hash ("md5", fileread (filepath));
834 %!
835 %! ## test with existing and non-existing directory
836 %! out_dirs = {tempname (test_dir), tempname (test_dir)};
837 %! if (! mkdir (out_dirs{1}))
838 %! error ("unable to create directory for test");
839 %! endif
840 %! unwind_protect
841 %! for idx = 1:numel (out_dirs)
842 %! out_dir = out_dirs{idx};
843 %! uz_file = fullfile (out_dir, filename);
844 %! z_file = [uz_file z.ext];
845 %!
846 %! z_filelist = z.zip (filepath, out_dir);
847 %! assert (z_filelist, {z_file})
848 %! assert (exist (z_file, "file"), 2)
849 %!
850 %! uz_filelist = z.unzip (z_file);
851 %! assert (is_same_file (uz_filelist, {uz_file})) # bug #48598
852 %!
853 %! assert (hash ("md5", fileread (uz_file)), md5)
854 %! endfor
855 %! unwind_protect_cleanup
856 %! confirm_recursive_rmdir (false, "local");
857 %! for idx = 1:numel (out_dirs)
858 %! sts = rmdir (out_dirs{idx}, "s");
859 %! endfor
860 %! end_unwind_protect
861 %!endfunction
862 %!test run_test_function (@test_save_to_dir)
863 */
864 
OCTAVE_END_NAMESPACE(octave)
ComplexNDArray concat(NDArray &ra, ComplexNDArray &rb, const Array< octave_idx_type > &ra_idx)
Definition: CNDArray.cc:418
OCTARRAY_OVERRIDABLE_FUNC_API octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:414
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(void)=delete
std::FILE * m_fp
Definition: gzip.cc:128
~CFile(void)
Definition: gzip.cc:114
Definition: Cell.h:43
Definition: oct-env.h:40
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
Definition: oct-time.h:52
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
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
int unlink(const std::string &name)
Definition: file-ops.cc:677
std::string dir_sep_str(void)
Definition: file-ops.cc:240
int mkdir(const std::string &nm, mode_t md)
Definition: file-ops.cc:402
std::string tilde_expand(const std::string &name)
Definition: file-ops.cc:283
string_vector xzip(const Array< std::string > &source_patterns, const std::function< std::string(const std::string &)> &mk_dest_path)
Definition: gzip.cc:479
std::FILE * fopen(const std::string &filename, const std::string &mode)
Definition: lo-sysdep.cc:314
bool get_dirlist(const std::string &dirname, string_vector &dirlist, std::string &msg)
Definition: lo-sysdep.cc:119
static std::string basename(const std::string &s, bool strip_path=false)
return octave_value(v1.char_array_value() . concat(v2.char_array_value(), ra_idx),((a1.is_sq_string()||a2.is_sq_string()) ? '\'' :'"'))