GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/cache.cc Lines: 95 114 83.3 %
Date: 2019-02-03 02:48:13 Branches: 44 68 64.7 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 */
4
#include "cvmfs_config.h"
5
#include "cache.h"
6
7
#include <alloca.h>
8
#include <errno.h>
9
10
#include <cassert>
11
#include <cstdlib>
12
#include <string>
13
14
#include "compression.h"
15
#include "directory_entry.h"
16
#include "download.h"
17
#include "hash.h"
18
#include "quota.h"
19
#include "smalloc.h"
20
#include "util/posix.h"
21
22
using namespace std;  // NOLINT
23
24
const uint64_t CacheManager::kSizeUnknown = uint64_t(-1);
25
26
27
362
CacheManager::CacheManager() : quota_mgr_(new NoopQuotaManager()) { }
28
29
30
357
CacheManager::~CacheManager() {
31
357
  delete quota_mgr_;
32
357
}
33
34
35
/**
36
 * Compresses and checksums the file pointed to by fd.  The hash algorithm needs
37
 * to be set in id.
38
 */
39
4
int CacheManager::ChecksumFd(int fd, shash::Any *id) {
40
4
  shash::ContextPtr hash_context(id->algorithm);
41
4
  hash_context.buffer = alloca(hash_context.size);
42
4
  shash::Init(hash_context);
43
44
  z_stream strm;
45
4
  zlib::CompressInit(&strm);
46
  zlib::StreamStates retval;
47
48
  unsigned char buf[4096];
49
4
  uint64_t pos = 0;
50
  bool eof;
51
52
4
  do {
53
5
    int64_t nbytes = Pread(fd, buf, 4096, pos);
54
5
    if (nbytes < 0) {
55
1
      zlib::CompressFini(&strm);
56
1
      return nbytes;
57
    }
58
4
    pos += nbytes;
59
4
    eof = nbytes < 4096;
60
4
    retval = zlib::CompressZStream2Null(buf, nbytes, eof, &strm, &hash_context);
61
4
    if (retval == zlib::kStreamDataError) {
62
      zlib::CompressFini(&strm);
63
      return -EINVAL;
64
    }
65
  } while (!eof);
66
67
3
  zlib::CompressFini(&strm);
68
3
  if (retval != zlib::kStreamEnd)
69
    return -EINVAL;
70
3
  shash::Final(hash_context, id);
71
3
  return 0;
72
}
73
74
75
/**
76
 * Commits the memory blob buffer to the given chunk id.  No checking!
77
 * The hash and the memory blob need to match.
78
 */
79
2129
bool CacheManager::CommitFromMem(
80
  const shash::Any &id,
81
  const unsigned char *buffer,
82
  const uint64_t size,
83
  const string &description)
84
{
85
2129
  void *txn = alloca(this->SizeOfTxn());
86
2129
  int fd = this->StartTxn(id, size, txn);
87
2129
  if (fd < 0)
88
1
    return false;
89
2128
  this->CtrlTxn(ObjectInfo(kTypeRegular, description), 0, txn);
90
2128
  int64_t retval = this->Write(buffer, size, txn);
91

2128
  if ((retval < 0) || (static_cast<uint64_t>(retval) != size)) {
92
1
    this->AbortTxn(txn);
93
1
    return false;
94
  }
95
2127
  retval = this->CommitTxn(txn);
96
2127
  return retval == 0;
97
}
98
99
100
4
void CacheManager::FreeState(const int fd_progress, void *data) {
101
4
  State *state = reinterpret_cast<State *>(data);
102
4
  if (fd_progress >= 0)
103
2
    SendMsg2Socket(fd_progress, "Releasing saved open files table\n");
104
4
  assert(state->version == kStateVersion);
105
4
  assert(state->manager_type == id());
106
4
  bool result = DoFreeState(state->concrete_state);
107
4
  if (!result) {
108
    if (fd_progress >= 0) {
109
      SendMsg2Socket(fd_progress,
110
                     "   *** Releasing open files table failed!\n");
111
    }
112
    abort();
113
  }
114
4
  delete state;
115
4
}
116
117
118
/**
119
 * Tries to open a file and copies its contents into a newly malloc'd
120
 * memory area.  User of the function has to free buffer (if successful).
121
 *
122
 * @param[in] id content hash of the catalog entry.
123
 * @param[out] buffer Contents of the file
124
 * @param[out] size Size of the file
125
 * \return True if successful, false otherwise.
126
 */
127
58
bool CacheManager::Open2Mem(
128
  const shash::Any &id,
129
  const std::string &description,
130
  unsigned char **buffer,
131
  uint64_t *size)
132
{
133
58
  *size = 0;
134
58
  *buffer = NULL;
135
136
58
  int fd = this->Open(Bless(id, kTypeRegular, description));
137
58
  if (fd < 0)
138
18
    return false;
139
140
40
  int64_t s = this->GetSize(fd);
141
40
  assert(s >= 0);
142
40
  *size = static_cast<uint64_t>(s);
143
144
40
  int64_t retval = 0;
145
40
  if (*size > 0) {
146
36
    *buffer = static_cast<unsigned char *>(smalloc(*size));
147
36
    retval = this->Pread(fd, *buffer, *size, 0);
148
  } else {
149
4
    *buffer = NULL;
150
  }
151
152
40
  this->Close(fd);
153

40
  if ((retval < 0) || (static_cast<uint64_t>(retval) != *size)) {
154
1
    free(*buffer);
155
1
    *buffer = NULL;
156
1
    *size = 0;
157
1
    return false;
158
  }
159
39
  return true;
160
}
161
162
163
/**
164
 * Uses the regular open and, if the file exists in the cache, pins it.  There
165
 * is a race condition: the file can be removed between the open and the Pin.
166
 * This is fixed by the quota manager's unpin method that removes files which
167
 * do not exist anymore in the cache.  (The quota manager also translates double
168
 * pins into a no-op, so that the accounting does not get out of sync.)
169
 */
170
142
int CacheManager::OpenPinned(
171
  const shash::Any &id,
172
  const string &description,
173
  bool is_catalog)
174
{
175
142
  ObjectInfo object_info(is_catalog ? kTypeCatalog : kTypeRegular, description);
176
142
  int fd = this->Open(Bless(id, object_info));
177
142
  if (fd >= 0) {
178
29
    int64_t size = this->GetSize(fd);
179
29
    if (size < 0) {
180
      this->Close(fd);
181
      return size;
182
    }
183
    bool retval =
184
29
      quota_mgr_->Pin(id, static_cast<uint64_t>(size), description, is_catalog);
185
29
    if (!retval) {
186
1
      this->Close(fd);
187
1
      return -ENOSPC;
188
    }
189
  }
190
141
  return fd;
191
}
192
193
194
4
void CacheManager::RestoreState(const int fd_progress, void *data) {
195
4
  State *state = reinterpret_cast<State *>(data);
196
4
  if (fd_progress >= 0)
197
2
    SendMsg2Socket(fd_progress, "Restoring open files table... ");
198
4
  if (state->version != kStateVersion) {
199
    if (fd_progress >= 0)
200
      SendMsg2Socket(fd_progress, "unsupported state version!\n");
201
    abort();
202
  }
203
4
  if (state->manager_type != id()) {
204
    if (fd_progress >= 0)
205
      SendMsg2Socket(fd_progress, "switching cache manager unsupported!\n");
206
    abort();
207
  }
208
4
  bool result = DoRestoreState(state->concrete_state);
209
4
  if (!result) {
210
    if (fd_progress >= 0) SendMsg2Socket(fd_progress, "FAILED!\n");
211
    abort();
212
  }
213
4
  if (fd_progress >= 0) SendMsg2Socket(fd_progress, "done\n");
214
4
}
215
216
217
/**
218
 * The actual work is done in the concrete cache managers.
219
 */
220
4
void *CacheManager::SaveState(const int fd_progress) {
221
4
  if (fd_progress >= 0)
222
2
    SendMsg2Socket(fd_progress, "Saving open files table\n");
223
4
  State *state = new State();
224
4
  state->manager_type = id();
225
4
  state->concrete_state = DoSaveState();
226
4
  if (state->concrete_state == NULL) {
227
    if (fd_progress >= 0) {
228
      SendMsg2Socket(fd_progress,
229
        "  *** This cache manager does not support saving state!\n");
230
    }
231
    abort();
232
  }
233
4
  return state;
234
}