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