GNU Octave  8.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 Properties Friends Macros Pages
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()) ? '\'' :'"'))