GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/compression.cc Lines: 402 505 79.6 %
Date: 2019-02-03 02:48:13 Branches: 145 269 53.9 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 *
4
 * This is a wrapper around zlib.  It provides
5
 * a set of functions to conveniently compress and decompress stuff.
6
 * Allmost all of the functions return true on success, otherwise false.
7
 *
8
 * TODO: think about code deduplication
9
 */
10
11
#include "cvmfs_config.h"
12
#include "compression.h"
13
14
#include <alloca.h>
15
#include <stdlib.h>
16
#include <sys/stat.h>
17
18
#include <algorithm>
19
#include <cassert>
20
#include <cstring>
21
22
#include "hash.h"
23
#include "logging.h"
24
#include "platform.h"
25
#include "smalloc.h"
26
#include "util/posix.h"
27
28
using namespace std;  // NOLINT
29
30
31
540
static bool CopyFile2File(FILE *fsrc, FILE *fdest) {
32
  unsigned char buf[1024];
33
540
  rewind(fsrc);
34
540
  rewind(fdest);
35
36
  size_t have;
37
799553
  do {
38
799553
    have = fread(buf, 1, 1024, fsrc);
39
799553
    if (fwrite(buf, 1, have, fdest) != have)
40
      return false;
41
  } while (have == 1024);
42
540
  return true;
43
}
44
45
3
bool CopyPath2File(const std::string &src, FILE *fdest) {
46
3
  int retval = -1;
47
  platform_stat64 info;
48
49
3
  FILE *fsrc = fopen(src.c_str(), "r");
50
3
  if (!fsrc) goto file_copy_final;
51
52
3
  if (!CopyFile2File(fsrc, fdest)) goto file_copy_final;
53
3
  retval = platform_fstat(fileno(fsrc), &info);
54
3
  retval |= fchmod(fileno(fdest), info.st_mode);
55
56
 file_copy_final:
57
3
  if (fsrc) fclose(fsrc);
58
3
  return retval == 0;
59
}
60
61
62
537
bool CopyPath2Path(const string &src, const string &dest) {
63
537
  FILE *fsrc = NULL;
64
537
  FILE *fdest = NULL;
65
537
  int retval = -1;
66
  platform_stat64 info;
67
68
537
  fsrc = fopen(src.c_str(), "r");
69
537
  if (!fsrc) goto file_copy_final;
70
71
537
  fdest = fopen(dest.c_str(), "w");
72
537
  if (!fdest) goto file_copy_final;
73
74
537
  if (!CopyFile2File(fsrc, fdest)) goto file_copy_final;
75
537
  retval = platform_fstat(fileno(fsrc), &info);
76
537
  retval |= fchmod(fileno(fdest), info.st_mode);
77
78
 file_copy_final:
79
537
  if (fsrc) fclose(fsrc);
80
537
  if (fdest) fclose(fdest);
81
537
  return retval == 0;
82
}
83
84
85
bool CopyMem2File(const unsigned char *buffer, const unsigned buffer_size,
86
                  FILE *fdest)
87
{
88
  int written = fwrite(buffer, 1, buffer_size, fdest);
89
  return (written >=0) && (unsigned(written) == buffer_size);
90
}
91
92
93
193
bool CopyMem2Path(const unsigned char *buffer, const unsigned buffer_size,
94
                  const string &path)
95
{
96
193
  int fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, kDefaultFileMode);
97
193
  if (fd < 0)
98
    return false;
99
100
193
  int written = write(fd, buffer, buffer_size);
101
193
  close(fd);
102
103

193
  return (written >=0) && (unsigned(written) == buffer_size);
104
}
105
106
107
10
bool CopyPath2Mem(const string &path,
108
                  unsigned char **buffer, unsigned *buffer_size)
109
{
110
10
  const int fd = open(path.c_str(), O_RDONLY);
111
10
  if (fd < 0)
112
    return false;
113
114
10
  *buffer_size = 512;
115
10
  *buffer = reinterpret_cast<unsigned char *>(smalloc(*buffer_size));
116
10
  unsigned total_bytes = 0;
117
10
  while (true) {
118
20
    int num_bytes = read(fd, *buffer + total_bytes, *buffer_size - total_bytes);
119
20
    if (num_bytes == 0)
120
10
      break;
121
10
    if (num_bytes < 0) {
122
      close(fd);
123
      free(*buffer);
124
      *buffer_size = 0;
125
      return false;
126
    }
127
10
    total_bytes += num_bytes;
128
10
    if (total_bytes >= *buffer_size) {
129
      *buffer_size *= 2;
130
      *buffer =
131
        reinterpret_cast<unsigned char *>(srealloc(*buffer, *buffer_size));
132
    }
133
  }
134
135
10
  close(fd);
136
10
  *buffer_size = total_bytes;
137
10
  return true;
138
}
139
140
namespace zlib {
141
142
const unsigned kBufferSize = 32768;
143
144
/**
145
 * Aborts if string doesn't match any of the algorithms.
146
 */
147
Algorithms ParseCompressionAlgorithm(const std::string &algorithm_option) {
148
  if ((algorithm_option == "default") || (algorithm_option == "zlib"))
149
    return kZlibDefault;
150
  if (algorithm_option == "none")
151
    return kNoCompression;
152
  LogCvmfs(kLogCompress, kLogStderr, "unknown compression algorithms: %s",
153
           algorithm_option.c_str());
154
  assert(false);
155
}
156
157
158
std::string AlgorithmName(const zlib::Algorithms alg) {
159
  switch (alg) {
160
    case kZlibDefault:
161
      return "zlib";
162
      break;
163
    case kNoCompression:
164
      return "none";
165
      break;
166
    // Purposely did not add a 'default' statement here: this will
167
    // cause the compiler to generate a warning if a new algorithm
168
    // is added but this function is not updated.
169
  }
170
  return "unknown";
171
}
172
173
174
288
void CompressInit(z_stream *strm) {
175
288
  strm->zalloc = Z_NULL;
176
288
  strm->zfree = Z_NULL;
177
288
  strm->opaque = Z_NULL;
178
288
  strm->next_in = Z_NULL;
179
288
  strm->avail_in = 0;
180
288
  int retval = deflateInit(strm, Z_DEFAULT_COMPRESSION);
181
288
  assert(retval == 0);
182
288
}
183
184
185
296
void DecompressInit(z_stream *strm) {
186
296
  strm->zalloc = Z_NULL;
187
296
  strm->zfree = Z_NULL;
188
296
  strm->opaque = Z_NULL;
189
296
  strm->avail_in = 0;
190
296
  strm->next_in = Z_NULL;
191
296
  int retval = inflateInit(strm);
192
296
  assert(retval == 0);
193
296
}
194
195
196
288
void CompressFini(z_stream *strm) {
197
288
  (void)deflateEnd(strm);
198
288
}
199
200
201
292
void DecompressFini(z_stream *strm) {
202
292
  (void)inflateEnd(strm);
203
292
}
204
205
206
4
StreamStates CompressZStream2Null(
207
  const void *buf,
208
  const int64_t size,
209
  const bool eof,
210
  z_stream *strm,
211
  shash::ContextPtr *hash_context)
212
{
213
  unsigned char out[kZChunk];
214
  int z_ret;
215
216
4
  strm->avail_in = size;
217
4
  strm->next_in = static_cast<unsigned char *>(const_cast<void *>(buf));
218
  // Run deflate() on input until output buffer not full, finish
219
  // compression if all of source has been read in
220
4
  do {
221
4
    strm->avail_out = kZChunk;
222
4
    strm->next_out = out;
223
4
    z_ret = deflate(strm, eof ? Z_FINISH : Z_NO_FLUSH);  // no bad return value
224
4
    if (z_ret == Z_STREAM_ERROR)
225
      return kStreamDataError;
226
4
    size_t have = kZChunk - strm->avail_out;
227
4
    shash::Update(out, have, *hash_context);
228
  } while (strm->avail_out == 0);
229
230
4
  return (z_ret == Z_STREAM_END ? kStreamEnd : kStreamContinue);
231
}
232
233
234
72
StreamStates DecompressZStream2Sink(
235
  const void *buf,
236
  const int64_t size,
237
  z_stream *strm,
238
  cvmfs::Sink *sink)
239
{
240
  unsigned char out[kZChunk];
241
  int z_ret;
242
72
  int64_t pos = 0;
243
244
72
  do {
245
72
    strm->avail_in = (kZChunk > (size-pos)) ? size-pos : kZChunk;
246
72
    strm->next_in = ((unsigned char *)buf)+pos;
247
248
    // Run inflate() on input until output buffer not full
249
101
    do {
250
101
      strm->avail_out = kZChunk;
251
101
      strm->next_out = out;
252
101
      z_ret = inflate(strm, Z_NO_FLUSH);
253

101
      switch (z_ret) {
254
        case Z_NEED_DICT:
255
          z_ret = Z_DATA_ERROR;  // and fall through
256
        case Z_STREAM_ERROR:
257
        case Z_DATA_ERROR:
258
          return kStreamDataError;
259
        case Z_MEM_ERROR:
260
          return kStreamIOError;
261
      }
262
101
      size_t have = kZChunk - strm->avail_out;
263
101
      int64_t written = sink->Write(out, have);
264

101
      if ((written < 0) || (static_cast<uint64_t>(written) != have))
265
        return kStreamIOError;
266
    } while (strm->avail_out == 0);
267
268
72
    pos += kZChunk;
269
  } while (pos < size);
270
271
72
  return (z_ret == Z_STREAM_END ? kStreamEnd : kStreamContinue);
272
}
273
274
275
34
StreamStates DecompressZStream2File(
276
  const void *buf,
277
  const int64_t size,
278
  z_stream *strm,
279
  FILE *f)
280
{
281
  unsigned char out[kZChunk];
282
  int z_ret;
283
34
  int64_t pos = 0;
284
285
34
  do {
286
34
    strm->avail_in = (kZChunk > (size-pos)) ? size-pos : kZChunk;
287
34
    strm->next_in = ((unsigned char *)buf)+pos;
288
289
    // Run inflate() on input until output buffer not full
290
58
    do {
291
58
      strm->avail_out = kZChunk;
292
58
      strm->next_out = out;
293
58
      z_ret = inflate(strm, Z_NO_FLUSH);
294

58
      switch (z_ret) {
295
        case Z_NEED_DICT:
296
          z_ret = Z_DATA_ERROR;  // and fall through
297
        case Z_STREAM_ERROR:
298
        case Z_DATA_ERROR:
299
          return kStreamDataError;
300
        case Z_MEM_ERROR:
301
          return kStreamIOError;
302
      }
303
58
      size_t have = kZChunk - strm->avail_out;
304

58
      if (fwrite(out, 1, have, f) != have || ferror(f)) {
305
        LogCvmfs(kLogCompress, kLogDebug, "Inflate to file failed with %s "
306
             "(errno=%d)", strerror(errno), errno);
307
        return kStreamIOError;
308
      }
309
    } while (strm->avail_out == 0);
310
311
34
    pos += kZChunk;
312
  } while (pos < size);
313
314
34
  return (z_ret == Z_STREAM_END ? kStreamEnd : kStreamContinue);
315
}
316
317
318
40
bool CompressPath2Path(const string &src, const string &dest) {
319
40
  FILE *fsrc = fopen(src.c_str(), "r");
320
40
  if (!fsrc) {
321
    LogCvmfs(kLogCompress, kLogDebug,  "open %s as compression source failed",
322
             src.c_str());
323
    return false;
324
  }
325
326
40
  FILE *fdest = fopen(dest.c_str(), "w");
327
40
  if (!fdest) {
328
    LogCvmfs(kLogCompress, kLogDebug, "open %s as compression destination  "
329
             "failed with errno=%d", dest.c_str(), errno);
330
    fclose(fsrc);
331
    return false;
332
  }
333
334
  LogCvmfs(kLogCompress, kLogDebug, "opened %s and %s for compression",
335
40
           src.c_str(), dest.c_str());
336
40
  const bool result = CompressFile2File(fsrc, fdest);
337
338
40
  fclose(fsrc);
339
40
  fclose(fdest);
340
40
  return result;
341
}
342
343
344
100
bool CompressPath2Path(const string &src, const string &dest,
345
                       shash::Any *compressed_hash)
346
{
347
100
  FILE *fsrc = fopen(src.c_str(), "r");
348
100
  if (!fsrc) {
349
    LogCvmfs(kLogCompress, kLogDebug, "open %s as compression source failed",
350
             src.c_str());
351
    return false;
352
  }
353
354
100
  FILE *fdest = fopen(dest.c_str(), "w");
355
100
  if (!fdest) {
356
    LogCvmfs(kLogCompress, kLogDebug, "open %s as compression destination "
357
             "failed with errno=%d", dest.c_str(), errno);
358
    fclose(fsrc);
359
    return false;
360
  }
361
362
  LogCvmfs(kLogCompress, kLogDebug, "opened %s and %s for compression",
363
100
           src.c_str(), dest.c_str());
364
100
  bool result = false;
365
100
  if (!CompressFile2File(fsrc, fdest, compressed_hash))
366
    goto compress_path2path_final;
367
  platform_stat64 info;
368
100
  if (platform_fstat(fileno(fsrc), &info) != 0) goto compress_path2path_final;
369
  // TODO(jakob): open in the right mode from the beginning
370
100
  if (fchmod(fileno(fdest), info.st_mode) != 0) goto compress_path2path_final;
371
372
100
  result = true;
373
374
 compress_path2path_final:
375
100
  fclose(fsrc);
376
100
  fclose(fdest);
377
100
  return result;
378
}
379
380
381
bool DecompressPath2Path(const string &src, const string &dest) {
382
  FILE *fsrc = NULL;
383
  FILE *fdest = NULL;
384
  int result = false;
385
386
  fsrc = fopen(src.c_str(), "r");
387
  if (!fsrc) goto decompress_path2path_final;
388
389
  fdest = fopen(dest.c_str(), "w");
390
  if (!fdest) goto decompress_path2path_final;
391
392
  result = DecompressFile2File(fsrc, fdest);
393
394
 decompress_path2path_final:
395
  if (fsrc) fclose(fsrc);
396
  if (fdest) fclose(fdest);
397
  return result;
398
}
399
400
401
40
bool CompressFile2Null(FILE *fsrc, shash::Any *compressed_hash) {
402
40
  int z_ret = 0;
403
40
  int flush = 0;
404
40
  bool result = -1;
405
  unsigned have;
406
  z_stream strm;
407
  unsigned char in[kZChunk];
408
  unsigned char out[kZChunk];
409
40
  shash::ContextPtr hash_context(compressed_hash->algorithm);
410
411
40
  CompressInit(&strm);
412
40
  hash_context.buffer = alloca(hash_context.size);
413
40
  shash::Init(hash_context);
414
415
  // Compress until end of file
416
40
  do {
417
40
    strm.avail_in = fread(in, 1, kZChunk, fsrc);
418
40
    if (ferror(fsrc)) goto compress_file2null_final;
419
420
40
    flush = feof(fsrc) ? Z_FINISH : Z_NO_FLUSH;
421
40
    strm.next_in = in;
422
423
    // Run deflate() on input until output buffer not full, finish
424
    // compression if all of source has been read in
425
40
    do {
426
40
      strm.avail_out = kZChunk;
427
40
      strm.next_out = out;
428
40
      z_ret = deflate(&strm, flush);  // no bad return value
429
40
      if (z_ret == Z_STREAM_ERROR)
430
        goto compress_file2null_final;  // state not clobbered
431
40
      have = kZChunk - strm.avail_out;
432
40
      shash::Update(out, have, hash_context);
433
    } while (strm.avail_out == 0);
434
435
    // Done when last data in file processed
436
  } while (flush != Z_FINISH);
437
438
  // stream will be complete
439
40
  if (z_ret != Z_STREAM_END) goto compress_file2null_final;
440
441
40
  shash::Final(hash_context, compressed_hash);
442
40
  result = true;
443
444
  // Clean up and return
445
 compress_file2null_final:
446
40
  CompressFini(&strm);
447
  LogCvmfs(kLogCompress, kLogDebug, "file compression finished with result %d",
448
40
           result);
449
40
  return result;
450
}
451
452
453
2
bool CompressFd2Null(int fd_src, shash::Any *compressed_hash,
454
                     uint64_t *processed_bytes) {
455
2
  int z_ret = 0;
456
2
  int flush = 0;
457
2
  bool result = false;
458
  unsigned have;
459
  z_stream strm;
460
  unsigned char in[kZChunk];
461
  unsigned char out[kZChunk];
462
2
  off_t cksum_bytes = 0;
463
2
  shash::ContextPtr hash_context(compressed_hash->algorithm);
464
465
2
  CompressInit(&strm);
466
2
  hash_context.buffer = alloca(hash_context.size);
467
2
  shash::Init(hash_context);
468
469
  // Compress until end of file
470
1
  do {
471
2
    ssize_t bytes_read = read(fd_src, in, kZChunk);
472
2
    if (bytes_read < 0) {
473
1
      if (errno == EINTR) {continue;}
474
1
      goto compress_fd2null_final;
475
    }
476
1
    cksum_bytes += bytes_read;
477
1
    strm.avail_in = bytes_read;
478
479
1
    flush = (static_cast<size_t>(bytes_read) < kZChunk) ? Z_FINISH : Z_NO_FLUSH;
480
1
    strm.next_in = in;
481
482
    // Run deflate() on input until output buffer not full, finish
483
    // compression if all of source has been read in
484
1
    do {
485
1
      strm.avail_out = kZChunk;
486
1
      strm.next_out = out;
487
1
      z_ret = deflate(&strm, flush);  // no bad return value
488
1
      if (z_ret == Z_STREAM_ERROR)
489
        goto compress_fd2null_final;  // state not clobbered
490
1
      have = kZChunk - strm.avail_out;
491
1
      shash::Update(out, have, hash_context);
492
    } while (strm.avail_out == 0);
493
494
    // Done when last data in file processed
495
  } while (flush != Z_FINISH);
496
497
  // stream will be complete
498
1
  if (z_ret != Z_STREAM_END) goto compress_fd2null_final;
499
500
1
  shash::Final(hash_context, compressed_hash);
501
1
  if (processed_bytes) {
502
1
    *processed_bytes = cksum_bytes;
503
  }
504
1
  result = true;
505
506
  // Clean up and return
507
 compress_fd2null_final:
508
2
  CompressFini(&strm);
509
  LogCvmfs(kLogCompress, kLogDebug, "file compression finished with result %d",
510
2
           result);
511
2
  return result;
512
}
513
514
515
40
bool CompressPath2Null(const string &src, shash::Any *compressed_hash) {
516
40
  FILE *fsrc = fopen(src.c_str(), "r");
517
40
  if (fsrc == NULL)
518
    return false;
519
40
  bool retval = CompressFile2Null(fsrc, compressed_hash);
520
40
  fclose(fsrc);
521
40
  return retval;
522
}
523
524
525
40
bool CompressFile2File(FILE *fsrc, FILE *fdest) {
526
40
  int z_ret = 0;
527
40
  int flush = 0;
528
40
  bool result = false;
529
  unsigned have;
530
  z_stream strm;
531
  unsigned char in[kZChunk];
532
  unsigned char out[kZChunk];
533
534
40
  CompressInit(&strm);
535
536
  // Compress until end of file
537
40
  do {
538
40
    strm.avail_in = fread(in, 1, kZChunk, fsrc);
539
40
    if (ferror(fsrc)) goto compress_file2file_final;
540
541
40
    flush = feof(fsrc) ? Z_FINISH : Z_NO_FLUSH;
542
40
    strm.next_in = in;
543
544
    // Run deflate() on input until output buffer not full, finish
545
    // compression if all of source has been read in
546
40
    do {
547
40
      strm.avail_out = kZChunk;
548
40
      strm.next_out = out;
549
40
      z_ret = deflate(&strm, flush);  // no bad return value
550
40
      if (z_ret == Z_STREAM_ERROR)
551
        goto compress_file2file_final;  // state not clobbered
552
40
      have = kZChunk - strm.avail_out;
553

40
      if (fwrite(out, 1, have, fdest) != have || ferror(fdest))
554
        goto compress_file2file_final;
555
    } while (strm.avail_out == 0);
556
557
    // Done when last data in file processed
558
  } while (flush != Z_FINISH);
559
560
  // stream will be complete
561
40
  if (z_ret != Z_STREAM_END) goto compress_file2file_final;
562
563
40
  result = true;
564
565
  // Clean up and return
566
 compress_file2file_final:
567
40
  CompressFini(&strm);
568
  LogCvmfs(kLogCompress, kLogDebug, "file compression finished with result %d",
569
40
           result);
570
40
  return result;
571
}
572
573
bool CompressPath2File(const string &src, FILE *fdest,
574
                       shash::Any *compressed_hash)
575
{
576
  FILE *fsrc = fopen(src.c_str(), "r");
577
  if (!fsrc)
578
    return false;
579
580
  bool retval = CompressFile2File(fsrc, fdest, compressed_hash);
581
  fclose(fsrc);
582
  return retval;
583
}
584
585
586
100
bool CompressFile2File(FILE *fsrc, FILE *fdest, shash::Any *compressed_hash) {
587
100
  int z_ret = 0;
588
100
  int flush = 0;
589
100
  bool result = false;
590
  unsigned have;
591
  z_stream strm;
592
  unsigned char in[kZChunk];
593
  unsigned char out[kZChunk];
594
100
  shash::ContextPtr hash_context(compressed_hash->algorithm);
595
596
100
  CompressInit(&strm);
597
100
  hash_context.buffer = alloca(hash_context.size);
598
100
  shash::Init(hash_context);
599
600
  // Compress until end of file
601
140
  do {
602
140
    strm.avail_in = fread(in, 1, kZChunk, fsrc);
603
140
    if (ferror(fsrc)) goto compress_file2file_hashed_final;
604
605
140
    flush = feof(fsrc) ? Z_FINISH : Z_NO_FLUSH;
606
140
    strm.next_in = in;
607
608
    // Run deflate() on input until output buffer not full, finish
609
    // compression if all of source has been read in
610
140
    do {
611
140
      strm.avail_out = kZChunk;
612
140
      strm.next_out = out;
613
140
      z_ret = deflate(&strm, flush);  // no bad return value
614
140
      if (z_ret == Z_STREAM_ERROR)
615
        goto compress_file2file_hashed_final;  // state not clobbered
616
140
      have = kZChunk - strm.avail_out;
617

140
      if (fwrite(out, 1, have, fdest) != have || ferror(fdest))
618
        goto compress_file2file_hashed_final;
619
140
      shash::Update(out, have, hash_context);
620
    } while (strm.avail_out == 0);
621
622
    // Done when last data in file processed
623
  } while (flush != Z_FINISH);
624
625
  // Stream will be complete
626
100
  if (z_ret != Z_STREAM_END) goto compress_file2file_hashed_final;
627
628
100
  shash::Final(hash_context, compressed_hash);
629
100
  result = true;
630
631
  // Clean up and return
632
 compress_file2file_hashed_final:
633
100
  CompressFini(&strm);
634
  LogCvmfs(kLogCompress, kLogDebug, "file compression finished with result %d",
635
100
           result);
636
100
  return result;
637
}
638
639
640
10
bool DecompressFile2File(FILE *fsrc, FILE *fdest) {
641
10
  bool result = false;
642
10
  StreamStates stream_state = kStreamIOError;
643
  z_stream strm;
644
  size_t have;
645
  unsigned char buf[kBufferSize];
646
647
10
  DecompressInit(&strm);
648
649
28
  while ((have = fread(buf, 1, kBufferSize, fsrc)) > 0) {
650
8
    stream_state = DecompressZStream2File(buf, have, &strm, fdest);
651

8
    if ((stream_state == kStreamDataError) || (stream_state == kStreamIOError))
652
      goto decompress_file2file_final;
653
  }
654
  LogCvmfs(kLogCompress, kLogDebug, "end of decompression, state=%d, error=%d",
655
10
           stream_state, ferror(fsrc));
656

10
  if ((stream_state != kStreamEnd) || ferror(fsrc))
657
2
    goto decompress_file2file_final;
658
659
8
  result = true;
660
661
 decompress_file2file_final:
662
10
  DecompressFini(&strm);
663
10
  return result;
664
}
665
666
667
10
bool DecompressPath2File(const string &src, FILE *fdest) {
668
10
  FILE *fsrc = fopen(src.c_str(), "r");
669
10
  if (!fsrc)
670
    return false;
671
672
10
  bool retval = DecompressFile2File(fsrc, fdest);
673
10
  fclose(fsrc);
674
10
  return retval;
675
}
676
677
678
1
bool CompressMem2File(const unsigned char *buf, const size_t size,
679
                      FILE *fdest, shash::Any *compressed_hash) {
680
1
  int z_ret = 0;
681
1
  int flush = 0;
682
1
  bool result = false;
683
  unsigned have;
684
  z_stream strm;
685
1
  size_t offset = 0;
686
1
  size_t used   = 0;
687
  unsigned char out[kZChunk];
688
1
  shash::ContextPtr hash_context(compressed_hash->algorithm);
689
690
1
  CompressInit(&strm);
691
1
  hash_context.buffer = alloca(hash_context.size);
692
1
  shash::Init(hash_context);
693
694
  // Compress the given memory buffer
695
5
  do {
696
5
    used = min((size_t)kZChunk, size - offset);
697
5
    strm.avail_in = used;
698
699
5
    flush = (strm.avail_in < kZChunk) ? Z_FINISH : Z_NO_FLUSH;
700
5
    strm.next_in = const_cast<unsigned char*>(buf + offset);
701
702
    // Run deflate() on input until output buffer not full, finish
703
    // compression if all of source has been read in
704
8
    do {
705
8
      strm.avail_out = kZChunk;
706
8
      strm.next_out = out;
707
8
      z_ret = deflate(&strm, flush);  // no bad return value
708
8
      if (z_ret == Z_STREAM_ERROR)
709
        goto compress_file2file_hashed_final;  // state not clobbered
710
8
      have = kZChunk - strm.avail_out;
711

8
      if (fwrite(out, 1, have, fdest) != have || ferror(fdest))
712
        goto compress_file2file_hashed_final;
713
8
      shash::Update(out, have, hash_context);
714
    } while (strm.avail_out == 0);
715
716
5
    offset += used;
717
718
    // Done when last data in file processed
719
  } while (flush != Z_FINISH);
720
721
  // Stream will be complete
722
1
  if (z_ret != Z_STREAM_END) goto compress_file2file_hashed_final;
723
724
1
  shash::Final(hash_context, compressed_hash);
725
1
  result = true;
726
727
  // Clean up and return
728
 compress_file2file_hashed_final:
729
1
  CompressFini(&strm);
730
  LogCvmfs(kLogCompress, kLogDebug, "file compression finished with result %d",
731
1
           result);
732
1
  return result;
733
}
734
735
736
/**
737
 * User of this function has to free out_buf.
738
 */
739
101
bool CompressMem2Mem(const void *buf, const int64_t size,
740
                    void **out_buf, uint64_t *out_size)
741
{
742
  unsigned char out[kZChunk];
743
  int z_ret;
744
  int flush;
745
  z_stream strm;
746
101
  int64_t pos = 0;
747
101
  uint64_t alloc_size = kZChunk;
748
749
101
  CompressInit(&strm);
750
101
  *out_buf = smalloc(alloc_size);
751
101
  *out_size = 0;
752
753
1124
  do {
754
1124
    strm.avail_in = (kZChunk > (size-pos)) ? size-pos : kZChunk;
755
1124
    flush = (pos + kZChunk) >= size ? Z_FINISH : Z_NO_FLUSH;
756
1124
    strm.next_in = ((unsigned char *)buf) + pos;
757
758
    // Run deflate() on input until output buffer not full
759
1140
    do {
760
1140
      strm.avail_out = kZChunk;
761
1140
      strm.next_out = out;
762
1140
      z_ret = deflate(&strm, flush);
763
1140
      if (z_ret == Z_STREAM_ERROR) {
764
        CompressFini(&strm);
765
        free(*out_buf);
766
        *out_buf = NULL;
767
        *out_size = 0;
768
        return false;
769
      }
770
1140
      size_t have = kZChunk - strm.avail_out;
771
1140
      if (*out_size+have > alloc_size) {
772
5
        alloc_size *= 2;
773
5
        *out_buf = srealloc(*out_buf, alloc_size);
774
      }
775
1140
      memcpy(static_cast<unsigned char *>(*out_buf) + *out_size, out, have);
776
1140
      *out_size += have;
777
    } while (strm.avail_out == 0);
778
779
1124
    pos += kZChunk;
780
  } while (flush != Z_FINISH);
781
782
101
  CompressFini(&strm);
783
101
  if (z_ret != Z_STREAM_END) {
784
    free(*out_buf);
785
    *out_buf = NULL;
786
    *out_size = 0;
787
    return false;
788
  } else {
789
101
    return true;
790
  }
791
}
792
793
794
/**
795
 * User of this function has to free out_buf.
796
 */
797
124
bool DecompressMem2Mem(const void *buf, const int64_t size,
798
                       void **out_buf, uint64_t *out_size)
799
{
800
  unsigned char out[kZChunk];
801
  int z_ret;
802
  z_stream strm;
803
124
  int64_t pos = 0;
804
124
  uint64_t alloc_size = kZChunk;
805
806
124
  DecompressInit(&strm);
807
124
  *out_buf = smalloc(alloc_size);
808
124
  *out_size = 0;
809
810
126
  do {
811
126
    strm.avail_in = (kZChunk > (size-pos)) ? size-pos : kZChunk;
812
126
    strm.next_in = ((unsigned char *)buf)+pos;
813
814
    // Run inflate() on input until output buffer not full
815
1404
    do {
816
1404
      strm.avail_out = kZChunk;
817
1404
      strm.next_out = out;
818
1404
      z_ret = inflate(&strm, Z_NO_FLUSH);
819
1404
      switch (z_ret) {
820
        case Z_NEED_DICT:
821
          z_ret = Z_DATA_ERROR;  // and fall through
822
        case Z_STREAM_ERROR:
823
        case Z_DATA_ERROR:
824
        case Z_MEM_ERROR:
825
          DecompressFini(&strm);
826
          free(*out_buf);
827
          *out_buf = NULL;
828
          *out_size = 0;
829
          return false;
830
      }
831
1404
      size_t have = kZChunk - strm.avail_out;
832
1404
      if (*out_size+have > alloc_size) {
833
11
        alloc_size *= 2;
834
11
        *out_buf = srealloc(*out_buf, alloc_size);
835
      }
836
1404
      memcpy(static_cast<unsigned char *>(*out_buf) + *out_size, out, have);
837
1404
      *out_size += have;
838
    } while (strm.avail_out == 0);
839
840
126
    pos += kZChunk;
841
  } while (pos < size);
842
843
124
  DecompressFini(&strm);
844
124
  if (z_ret != Z_STREAM_END) {
845
    free(*out_buf);
846
    *out_buf = NULL;
847
    *out_size = 0;
848
    return false;
849
  }
850
851
124
  return true;
852
}
853
854
855
//------------------------------------------------------------------------------
856
857
858
4
void Compressor::RegisterPlugins() {
859
4
  RegisterPlugin<ZlibCompressor>();
860
4
  RegisterPlugin<EchoCompressor>();
861
4
}
862
863
864
//------------------------------------------------------------------------------
865
866
867
6829
bool ZlibCompressor::WillHandle(const zlib::Algorithms &alg) {
868
6829
  return alg == kZlibDefault;
869
}
870
871
872
6825
ZlibCompressor::ZlibCompressor(const Algorithms &alg)
873
6825
  : Compressor(alg)
874
{
875
6825
  stream_.zalloc   = Z_NULL;
876
6825
  stream_.zfree    = Z_NULL;
877
6825
  stream_.opaque   = Z_NULL;
878
6825
  stream_.next_in  = Z_NULL;
879
6825
  stream_.avail_in = 0;
880
6825
  const int zlib_retval = deflateInit(&stream_, Z_DEFAULT_COMPRESSION);
881
6825
  assert(zlib_retval == 0);
882
}
883
884
885
Compressor* ZlibCompressor::Clone() {
886
  ZlibCompressor* other = new ZlibCompressor(zlib::kZlibDefault);
887
  assert(stream_.avail_in == 0);
888
  // Delete the other stream
889
  int retcode = deflateEnd(&other->stream_);
890
  assert(retcode == Z_OK);
891
  retcode = deflateCopy(const_cast<z_streamp>(&other->stream_), &stream_);
892
  assert(retcode == Z_OK);
893
  return other;
894
}
895
896
601068
bool ZlibCompressor::Deflate(
897
  const bool flush,
898
  unsigned char **inbuf, size_t *inbufsize,
899
  unsigned char **outbuf, size_t *outbufsize)
900
{
901
  // Adding compression
902
601068
  stream_.avail_in = *inbufsize;
903
601068
  stream_.next_in = *inbuf;
904
601068
  const int flush_int = (flush) ? Z_FINISH : Z_NO_FLUSH;
905
601068
  int retcode = 0;
906
907
  // TODO(jblomer) Figure out what exactly behaves differently with zlib 1.2.10
908
  // if ((*inbufsize == 0) && !flush)
909
  //   return true;
910
911
601068
  stream_.avail_out = *outbufsize;
912
601068
  stream_.next_out = *outbuf;
913
914
  // Deflate in zlib!
915
601068
  retcode = deflate(&stream_, flush_int);
916

601080
  assert(retcode == Z_OK || retcode == Z_STREAM_END);
917
918
601080
  *outbufsize -= stream_.avail_out;
919
601080
  *inbuf = stream_.next_in;
920
601080
  *inbufsize = stream_.avail_in;
921
922
  return (flush_int == Z_NO_FLUSH && retcode == Z_OK && stream_.avail_in == 0)
923


601080
         || (flush_int == Z_FINISH  && retcode == Z_STREAM_END);
924
}
925
926
927
13650
ZlibCompressor::~ZlibCompressor() {
928
6825
  int retcode = deflateEnd(&stream_);
929
6825
  assert(retcode == Z_OK);
930
6825
}
931
932
933
1
size_t ZlibCompressor::DeflateBound(const size_t bytes) {
934
  // Call zlib's deflate bound
935
1
  return deflateBound(&stream_, bytes);
936
}
937
938
939
//------------------------------------------------------------------------------
940
941
942
4
EchoCompressor::EchoCompressor(const zlib::Algorithms &alg):
943
4
  Compressor(alg)
944
{
945
4
}
946
947
948
4
bool EchoCompressor::WillHandle(const zlib::Algorithms &alg) {
949
4
  return alg == kNoCompression;
950
}
951
952
953
Compressor* EchoCompressor::Clone() {
954
  return new EchoCompressor(zlib::kNoCompression);
955
}
956
957
958
209718
bool EchoCompressor::Deflate(
959
  const bool flush,
960
  unsigned char **inbuf, size_t *inbufsize,
961
  unsigned char **outbuf, size_t *outbufsize)
962
{
963
209718
  size_t bytes_to_copy = min(*outbufsize, *inbufsize);
964
209718
  memcpy(*outbuf, *inbuf, bytes_to_copy);
965
209718
  const bool done = (bytes_to_copy == *inbufsize);
966
967
  // Update the return variables
968
209718
  *inbuf += bytes_to_copy;
969
209718
  *outbufsize = bytes_to_copy;
970
209718
  *inbufsize -= bytes_to_copy;
971
972
209718
  return done;
973
}
974
975
976
1
size_t EchoCompressor::DeflateBound(const size_t bytes) {
977
  // zero bytes as an upper bound is no good because some callers want to
978
  // allocate buffers according to this value
979
1
  return (bytes == 0) ? 1 : bytes;
980
}
981
982

51
}  // namespace zlib