GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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()) ? '\'' :'"'))