1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "puffin/src/puffin_stream.h"
6 #include <fcntl.h>
7 #include <sys/stat.h>
8
9 #include <algorithm>
10 #include <filesystem>
11 #include <memory>
12 #include <system_error>
13 #include <utility>
14 #include <vector>
15
16 #include "puffin/src/bit_reader.h"
17 #include "puffin/src/bit_writer.h"
18 #include "puffin/src/include/puffin/common.h"
19 #include "puffin/src/include/puffin/huffer.h"
20 #include "puffin/src/include/puffin/puffer.h"
21 #include "puffin/src/include/puffin/stream.h"
22 #include "puffin/src/logging.h"
23 #include "puffin/src/puff_reader.h"
24 #include "puffin/src/puff_writer.h"
25
26 using std::shared_ptr;
27 using std::unique_ptr;
28 using std::vector;
29
30 namespace puffin {
31
32 namespace {
33
CheckArgsIntegrity(uint64_t puff_size,const vector<BitExtent> & deflates,const vector<ByteExtent> & puffs)34 bool CheckArgsIntegrity(uint64_t puff_size,
35 const vector<BitExtent>& deflates,
36 const vector<ByteExtent>& puffs) {
37 TEST_AND_RETURN_FALSE(puffs.size() == deflates.size());
38 // Check if the |puff_size| is actually greater than the last byte of the last
39 // puff in |puffs|.
40 if (!puffs.empty()) {
41 TEST_AND_RETURN_FALSE(puff_size >=
42 puffs.back().offset + puffs.back().length);
43 }
44
45 // Check to make sure |puffs| and |deflates| are sorted and non-overlapping.
46 auto is_overlap = [](const auto& a, const auto& b) {
47 return (a.offset + a.length) > b.offset;
48 };
49 TEST_AND_RETURN_FALSE(deflates.end() == std::adjacent_find(deflates.begin(),
50 deflates.end(),
51 is_overlap));
52 TEST_AND_RETURN_FALSE(puffs.end() == std::adjacent_find(puffs.begin(),
53 puffs.end(),
54 is_overlap));
55 return true;
56 }
57
58 } // namespace
59
CreateForPuff(UniqueStreamPtr stream,shared_ptr<Puffer> puffer,uint64_t puff_size,const vector<BitExtent> & deflates,const vector<ByteExtent> & puffs,size_t max_cache_size)60 UniqueStreamPtr PuffinStream::CreateForPuff(UniqueStreamPtr stream,
61 shared_ptr<Puffer> puffer,
62 uint64_t puff_size,
63 const vector<BitExtent>& deflates,
64 const vector<ByteExtent>& puffs,
65 size_t max_cache_size) {
66 TEST_AND_RETURN_VALUE(CheckArgsIntegrity(puff_size, deflates, puffs),
67 nullptr);
68 TEST_AND_RETURN_VALUE(stream->Seek(0), nullptr);
69
70 UniqueStreamPtr puffin_stream(new PuffinStream(std::move(stream), puffer,
71 nullptr, puff_size, deflates,
72 puffs, max_cache_size));
73 TEST_AND_RETURN_VALUE(puffin_stream->Seek(0), nullptr);
74 return puffin_stream;
75 }
76
CreateForHuff(UniqueStreamPtr stream,shared_ptr<Huffer> huffer,uint64_t puff_size,const vector<BitExtent> & deflates,const vector<ByteExtent> & puffs)77 UniqueStreamPtr PuffinStream::CreateForHuff(UniqueStreamPtr stream,
78 shared_ptr<Huffer> huffer,
79 uint64_t puff_size,
80 const vector<BitExtent>& deflates,
81 const vector<ByteExtent>& puffs) {
82 TEST_AND_RETURN_VALUE(CheckArgsIntegrity(puff_size, deflates, puffs),
83 nullptr);
84 TEST_AND_RETURN_VALUE(stream->Seek(0), nullptr);
85
86 UniqueStreamPtr puffin_stream(new PuffinStream(
87 std::move(stream), nullptr, huffer, puff_size, deflates, puffs, 0));
88 TEST_AND_RETURN_VALUE(puffin_stream->Seek(0), nullptr);
89 return puffin_stream;
90 }
91
PuffinStream(UniqueStreamPtr stream,shared_ptr<Puffer> puffer,shared_ptr<Huffer> huffer,uint64_t puff_size,const vector<BitExtent> & deflates,const vector<ByteExtent> & puffs,size_t max_cache_size)92 PuffinStream::PuffinStream(UniqueStreamPtr stream,
93 shared_ptr<Puffer> puffer,
94 shared_ptr<Huffer> huffer,
95 uint64_t puff_size,
96 const vector<BitExtent>& deflates,
97 const vector<ByteExtent>& puffs,
98 size_t max_cache_size)
99 : stream_(std::move(stream)),
100 puffer_(puffer),
101 huffer_(huffer),
102 puff_stream_size_(puff_size),
103 deflates_(deflates),
104 puffs_(puffs),
105 puff_pos_(0),
106 skip_bytes_(0),
107 deflate_bit_pos_(0),
108 last_byte_(0),
109 extra_byte_(0),
110 is_for_puff_(puffer_ ? true : false),
111 closed_(false),
112 lru_cache_(max_cache_size) {
113 // Building upper bounds for faster seek.
114 upper_bounds_.reserve(puffs.size());
115 for (const auto& puff : puffs) {
116 upper_bounds_.emplace_back(puff.offset + puff.length);
117 }
118 upper_bounds_.emplace_back(puff_stream_size_ + 1);
119
120 // We can pass the size of the deflate stream too, but it is not necessary
121 // yet. We cannot get the size of stream from itself, because we might be
122 // writing into it and its size is not defined yet.
123 uint64_t deflate_stream_size = puff_stream_size_;
124 if (!puffs.empty()) {
125 deflate_stream_size =
126 ((deflates.back().offset + deflates.back().length) / 8) +
127 puff_stream_size_ - (puffs.back().offset + puffs.back().length);
128 }
129
130 deflates_.emplace_back(deflate_stream_size * 8, 0);
131 puffs_.emplace_back(puff_stream_size_, 0);
132
133 // Look for the largest puff and deflate extents and get proper size buffers.
134 uint64_t max_puff_length = 0;
135 for (const auto& puff : puffs) {
136 max_puff_length = std::max(max_puff_length, puff.length);
137 }
138 puff_buffer_.reset(new Buffer(max_puff_length + 1));
139
140 uint64_t max_deflate_length = 0;
141 for (const auto& deflate : deflates) {
142 max_deflate_length = std::max(max_deflate_length, deflate.length * 8);
143 }
144 deflate_buffer_.reset(new Buffer(max_deflate_length + 2));
145 }
146
GetSize(uint64_t * size) const147 bool PuffinStream::GetSize(uint64_t* size) const {
148 *size = puff_stream_size_;
149 return true;
150 }
151
GetOffset(uint64_t * offset) const152 bool PuffinStream::GetOffset(uint64_t* offset) const {
153 *offset = puff_pos_ + skip_bytes_;
154 return true;
155 }
156
Seek(uint64_t offset)157 bool PuffinStream::Seek(uint64_t offset) {
158 TEST_AND_RETURN_FALSE(!closed_);
159 if (!is_for_puff_) {
160 // For huffing we should not seek, only seek to zero is accepted.
161 TEST_AND_RETURN_FALSE(offset == 0);
162 }
163
164 TEST_AND_RETURN_FALSE(offset <= puff_stream_size_);
165
166 // We are searching first available puff which either includes the |offset| or
167 // it is the next available puff after |offset|.
168 auto next_puff_iter =
169 std::upper_bound(upper_bounds_.begin(), upper_bounds_.end(), offset);
170 TEST_AND_RETURN_FALSE(next_puff_iter != upper_bounds_.end());
171 auto next_puff_idx = std::distance(upper_bounds_.begin(), next_puff_iter);
172 cur_puff_ = std::next(puffs_.begin(), next_puff_idx);
173 cur_deflate_ = std::next(deflates_.begin(), next_puff_idx);
174
175 if (offset < cur_puff_->offset) {
176 // between two puffs.
177 puff_pos_ = offset;
178 auto back_track_bytes = cur_puff_->offset - puff_pos_;
179 deflate_bit_pos_ = ((cur_deflate_->offset + 7) / 8 - back_track_bytes) * 8;
180 if (cur_puff_ != puffs_.begin()) {
181 auto prev_deflate = std::prev(cur_deflate_);
182 if (deflate_bit_pos_ < prev_deflate->offset + prev_deflate->length) {
183 deflate_bit_pos_ = prev_deflate->offset + prev_deflate->length;
184 }
185 }
186 } else {
187 // Inside a puff.
188 puff_pos_ = cur_puff_->offset;
189 deflate_bit_pos_ = cur_deflate_->offset;
190 }
191 skip_bytes_ = offset - puff_pos_;
192 if (!is_for_puff_ && offset == 0) {
193 TEST_AND_RETURN_FALSE(stream_->Seek(0));
194 TEST_AND_RETURN_FALSE(SetExtraByte());
195 }
196 return true;
197 }
198
Close()199 bool PuffinStream::Close() {
200 closed_ = true;
201 return stream_->Close();
202 }
203
Read(void * buffer,size_t count)204 bool PuffinStream::Read(void* buffer, size_t count) {
205 TEST_AND_RETURN_FALSE(!closed_);
206 TEST_AND_RETURN_FALSE(is_for_puff_);
207 if (cur_puff_ == puffs_.end()) {
208 TEST_AND_RETURN_FALSE(count == 0);
209 }
210 auto bytes = static_cast<uint8_t*>(buffer);
211 uint64_t length = count;
212 uint64_t bytes_read = 0;
213 while (bytes_read < length) {
214 if (puff_pos_ < cur_puff_->offset) {
215 // Reading between two deflates. We also read bytes that have at least one
216 // bit of a deflate bit stream. The byte which has both deflate and raw
217 // data will be shifted or masked off the deflate bits and the remaining
218 // value will be saved in the puff stream as an byte integer.
219 uint64_t start_byte = (deflate_bit_pos_ / 8);
220 uint64_t end_byte = (cur_deflate_->offset + 7) / 8;
221 auto bytes_to_read = std::min(length - bytes_read, end_byte - start_byte);
222 TEST_AND_RETURN_FALSE(bytes_to_read >= 1);
223
224 TEST_AND_RETURN_FALSE(stream_->Seek(start_byte));
225 TEST_AND_RETURN_FALSE(stream_->Read(bytes + bytes_read, bytes_to_read));
226
227 // If true, we read the first byte of the curret deflate. So we have to
228 // mask out the deflate bits (which are most significant bits.)
229 if ((start_byte + bytes_to_read) * 8 > cur_deflate_->offset) {
230 bytes[bytes_read + bytes_to_read - 1] &=
231 (1 << (cur_deflate_->offset & 7)) - 1;
232 }
233
234 // If true, we read the last byte of the previous deflate and we have to
235 // shift it out. The least significat bits belongs to the deflate
236 // stream. The order of these last two conditions are important because a
237 // byte can contain a finishing deflate and a starting deflate with some
238 // bits between them so we have to modify correctly. Keep in mind that in
239 // this situation both are modifying the same byte.
240 if (start_byte * 8 < deflate_bit_pos_) {
241 bytes[bytes_read] >>= deflate_bit_pos_ & 7;
242 }
243
244 // Pass |deflate_bit_pos_| for all the read bytes.
245 deflate_bit_pos_ -= deflate_bit_pos_ & 7;
246 deflate_bit_pos_ += bytes_to_read * 8;
247 if (deflate_bit_pos_ > cur_deflate_->offset) {
248 // In case it reads into the first byte of the current deflate.
249 deflate_bit_pos_ = cur_deflate_->offset;
250 }
251
252 bytes_read += bytes_to_read;
253 puff_pos_ += bytes_to_read;
254 TEST_AND_RETURN_FALSE(puff_pos_ <= cur_puff_->offset);
255 } else {
256 // Reading the deflate itself. We read all bytes including the first and
257 // last byte (which may partially include a deflate bit). Here we keep the
258 // |puff_pos_| point to the first byte of the puffed stream and
259 // |skip_bytes_| shows how many bytes in the puff we have copied till now.
260 auto start_byte = (cur_deflate_->offset / 8);
261 auto end_byte = (cur_deflate_->offset + cur_deflate_->length + 7) / 8;
262 auto bytes_to_read = end_byte - start_byte;
263 // Puff directly to buffer if it has space.
264 const bool puff_directly_into_buffer =
265 lru_cache_.capacity() == 0 && (skip_bytes_ == 0) &&
266 (length - bytes_read >= cur_puff_->length);
267
268 auto cur_puff_idx = std::distance(puffs_.begin(), cur_puff_);
269 if (lru_cache_.capacity() == 0 ||
270 !GetPuffCache(cur_puff_idx, cur_puff_->length, &puff_buffer_)) {
271 // Did not find the puff buffer in cache. We have to build it.
272 deflate_buffer_->resize(bytes_to_read);
273 TEST_AND_RETURN_FALSE(stream_->Seek(start_byte));
274 TEST_AND_RETURN_FALSE(
275 stream_->Read(deflate_buffer_->data(), bytes_to_read));
276 BufferBitReader bit_reader(deflate_buffer_->data(), bytes_to_read);
277
278 BufferPuffWriter puff_writer(puff_directly_into_buffer
279 ? bytes + bytes_read
280 : puff_buffer_->data(),
281 cur_puff_->length);
282
283 // Drop the first unused bits.
284 size_t extra_bits_len = cur_deflate_->offset & 7;
285 TEST_AND_RETURN_FALSE(bit_reader.CacheBits(extra_bits_len));
286 bit_reader.DropBits(extra_bits_len);
287
288 TEST_AND_RETURN_FALSE(
289 puffer_->PuffDeflate(&bit_reader, &puff_writer, nullptr));
290 TEST_AND_RETURN_FALSE(bytes_to_read == bit_reader.Offset());
291 TEST_AND_RETURN_FALSE(cur_puff_->length == puff_writer.Size());
292 } else {
293 // Just seek to proper location.
294 TEST_AND_RETURN_FALSE(stream_->Seek(start_byte + bytes_to_read));
295 }
296 // Copy from puff buffer to output if needed.
297 auto bytes_to_copy =
298 std::min(length - bytes_read, cur_puff_->length - skip_bytes_);
299 if (!puff_directly_into_buffer) {
300 memcpy(bytes + bytes_read, puff_buffer_->data() + skip_bytes_,
301 bytes_to_copy);
302 }
303
304 skip_bytes_ += bytes_to_copy;
305 bytes_read += bytes_to_copy;
306
307 // Move to next puff.
308 if (puff_pos_ + skip_bytes_ == cur_puff_->offset + cur_puff_->length) {
309 puff_pos_ += skip_bytes_;
310 skip_bytes_ = 0;
311 deflate_bit_pos_ = cur_deflate_->offset + cur_deflate_->length;
312 cur_puff_++;
313 cur_deflate_++;
314 if (cur_puff_ == puffs_.end()) {
315 break;
316 }
317 }
318 }
319 }
320
321 TEST_AND_RETURN_FALSE(bytes_read == length);
322 return true;
323 }
324
Write(const void * buffer,size_t count)325 bool PuffinStream::Write(const void* buffer, size_t count) {
326 TEST_AND_RETURN_FALSE(!closed_);
327 TEST_AND_RETURN_FALSE(!is_for_puff_);
328 auto bytes = static_cast<const uint8_t*>(buffer);
329 uint64_t length = count;
330 uint64_t bytes_wrote = 0;
331 while (bytes_wrote < length) {
332 if (deflate_bit_pos_ < (cur_deflate_->offset & ~7ull)) {
333 // Between two puffs or before the first puff. We know that we are
334 // starting from the byte boundary because we have already processed the
335 // non-deflate bits of the last byte of the last deflate. Here we don't
336 // process any byte that has deflate bit.
337 TEST_AND_RETURN_FALSE((deflate_bit_pos_ & 7) == 0);
338 auto copy_len =
339 std::min((cur_deflate_->offset / 8) - (deflate_bit_pos_ / 8),
340 length - bytes_wrote);
341 TEST_AND_RETURN_FALSE(stream_->Write(bytes + bytes_wrote, copy_len));
342 bytes_wrote += copy_len;
343 puff_pos_ += copy_len;
344 deflate_bit_pos_ += copy_len * 8;
345 } else {
346 // We are in a puff. We have to buffer incoming bytes until we reach the
347 // size of the current puff so we can huff :). If the last bit of the
348 // current deflate does not end in a byte boundary, then we have to read
349 // one more byte to fill up the last byte of the deflate stream before
350 // doing anything else.
351
352 // |deflate_bit_pos_| now should be in the same byte as
353 // |cur_deflate->offset|.
354 if (deflate_bit_pos_ < cur_deflate_->offset) {
355 last_byte_ |= bytes[bytes_wrote++] << (deflate_bit_pos_ & 7);
356 skip_bytes_ = 0;
357 deflate_bit_pos_ = cur_deflate_->offset;
358 puff_pos_++;
359 TEST_AND_RETURN_FALSE(puff_pos_ == cur_puff_->offset);
360 }
361
362 auto copy_len = std::min(length - bytes_wrote,
363 cur_puff_->length + extra_byte_ - skip_bytes_);
364 TEST_AND_RETURN_FALSE(puff_buffer_->size() >= skip_bytes_ + copy_len);
365 memcpy(puff_buffer_->data() + skip_bytes_, bytes + bytes_wrote, copy_len);
366 skip_bytes_ += copy_len;
367 bytes_wrote += copy_len;
368
369 if (skip_bytes_ == cur_puff_->length + extra_byte_) {
370 // |puff_buffer_| is full, now huff into the |deflate_buffer_|.
371 auto start_byte = cur_deflate_->offset / 8;
372 auto end_byte = (cur_deflate_->offset + cur_deflate_->length + 7) / 8;
373 auto bytes_to_write = end_byte - start_byte;
374
375 deflate_buffer_->resize(bytes_to_write);
376 BufferBitWriter bit_writer(deflate_buffer_->data(), bytes_to_write);
377 BufferPuffReader puff_reader(puff_buffer_->data(), cur_puff_->length);
378
379 // Write last byte if it has any.
380 TEST_AND_RETURN_FALSE(
381 bit_writer.WriteBits(cur_deflate_->offset & 7, last_byte_));
382 last_byte_ = 0;
383
384 TEST_AND_RETURN_FALSE(huffer_->HuffDeflate(&puff_reader, &bit_writer));
385 TEST_AND_RETURN_FALSE(bit_writer.Size() == bytes_to_write);
386 TEST_AND_RETURN_FALSE(puff_reader.BytesLeft() == 0);
387
388 deflate_bit_pos_ = cur_deflate_->offset + cur_deflate_->length;
389 if (extra_byte_ == 1) {
390 deflate_buffer_->data()[bytes_to_write - 1] |=
391 puff_buffer_->data()[cur_puff_->length] << (deflate_bit_pos_ & 7);
392 deflate_bit_pos_ = (deflate_bit_pos_ + 7) & ~7ull;
393 } else if ((deflate_bit_pos_ & 7) != 0) {
394 // This happens if current and next deflate finish and end on the same
395 // byte, then we cannot write into output until we have huffed the
396 // next puff buffer, so untill then we cache it into |last_byte_| and
397 // we won't write it out.
398 last_byte_ = deflate_buffer_->data()[bytes_to_write - 1];
399 bytes_to_write--;
400 }
401
402 // Write |deflate_buffer_| into output.
403 TEST_AND_RETURN_FALSE(
404 stream_->Write(deflate_buffer_->data(), bytes_to_write));
405
406 // Move to the next deflate/puff.
407 puff_pos_ += skip_bytes_;
408 skip_bytes_ = 0;
409 cur_puff_++;
410 cur_deflate_++;
411 if (cur_puff_ == puffs_.end()) {
412 break;
413 }
414 // Find if need an extra byte to cache at the end.
415 TEST_AND_RETURN_FALSE(SetExtraByte());
416 }
417 }
418 }
419
420 TEST_AND_RETURN_FALSE(bytes_wrote == length);
421 return true;
422 }
423
SetExtraByte()424 bool PuffinStream::SetExtraByte() {
425 TEST_AND_RETURN_FALSE(cur_deflate_ != deflates_.end());
426 if ((cur_deflate_ + 1) == deflates_.end()) {
427 extra_byte_ = 0;
428 return true;
429 }
430 uint64_t end_bit = cur_deflate_->offset + cur_deflate_->length;
431 if ((end_bit & 7) && ((end_bit + 7) & ~7ull) <= (cur_deflate_ + 1)->offset) {
432 extra_byte_ = 1;
433 } else {
434 extra_byte_ = 0;
435 }
436 return true;
437 }
438
GetPuffCache(int puff_id,uint64_t puff_size,shared_ptr<Buffer> * buffer)439 bool PuffinStream::GetPuffCache(int puff_id,
440 uint64_t puff_size,
441 shared_ptr<Buffer>* buffer) {
442 // Search for it.
443 shared_ptr<Buffer> cache = lru_cache_.get(puff_id);
444 const bool found = cache != nullptr;
445
446 // If not found, either create one or get one from the list.
447 if (!found) {
448 // If we have not populated the cache yet, create one.
449 lru_cache_.EnsureSpace(puff_size);
450 cache = std::make_shared<Buffer>(puff_size);
451
452 lru_cache_.put(puff_id, cache);
453 }
454
455 *buffer = std::move(cache);
456 return found;
457 }
458
get(const Key & key)459 const LRUCache::Value LRUCache::get(const Key& key) {
460 auto it = items_map_.find(key);
461 if (it == items_map_.end()) {
462 if (ondisk_items_.count(key) > 0) {
463 auto&& data = ReadFromDisk(key);
464 put(key, data);
465 return data;
466 }
467 return nullptr;
468 }
469 items_list_.splice(items_list_.begin(), items_list_, it->second);
470 return it->second->second;
471 }
472
ReadFromDisk(const LRUCache::Key & key)473 LRUCache::Value LRUCache::ReadFromDisk(const LRUCache::Key& key) {
474 const auto path = tmpdir_ / std::to_string(key);
475 int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
476 if (fd < 0) {
477 PLOG(ERROR) << "Failed to open " << path;
478 return {};
479 }
480 auto fd_delete = [](int* fd) { close(*fd); };
481 std::unique_ptr<int, decltype(fd_delete)> guard(&fd, fd_delete);
482 struct stat st {};
483 const int ret = stat(path.c_str(), &st);
484 if (ret < 0) {
485 PLOG(ERROR) << "Failed to stat " << path << " ret: " << ret;
486 return {};
487 }
488 LRUCache::Value data = std::make_shared<std::vector<uint8_t>>(st.st_size);
489 const auto bytes_read =
490 TEMP_FAILURE_RETRY(pread(fd, data->data(), data->size(), 0));
491 if (static_cast<size_t>(bytes_read) != data->size()) {
492 PLOG(ERROR) << "Failed to read " << data->size() << " bytes data from "
493 << path;
494 return {};
495 }
496 return data;
497 }
498
~LRUCache()499 LRUCache::~LRUCache() {
500 std::error_code ec;
501 std::filesystem::remove_all(tmpdir_, ec);
502 if (ec) {
503 LOG(ERROR) << "Failed to rm -rf " << tmpdir_ << " " << ec.message();
504 }
505 }
506
WriteToDisk(const LRUCache::Key & key,const LRUCache::Value & value)507 bool LRUCache::WriteToDisk(const LRUCache::Key& key,
508 const LRUCache::Value& value) {
509 if (tmpdir_.empty()) {
510 return false;
511 }
512 const auto path = tmpdir_ / std::to_string(key);
513 int fd = open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
514 if (fd < 0) {
515 PLOG(ERROR) << "Failed to open " << path;
516 return false;
517 }
518 auto fd_delete = [](int* fd) { close(*fd); };
519 std::unique_ptr<int, decltype(fd_delete)> guard(&fd, fd_delete);
520 const auto written =
521 TEMP_FAILURE_RETRY(write(fd, value->data(), value->size()));
522 if (static_cast<size_t>(written) != value->size()) {
523 PLOG(ERROR) << "Failed to write " << value->size() << " bytes data to "
524 << path;
525 return false;
526 }
527 close(fd);
528 guard.release();
529 ondisk_items_.insert(key);
530 return true;
531 }
532
put(const LRUCache::Key & key,const LRUCache::Value & value)533 void LRUCache::put(const LRUCache::Key& key, const LRUCache::Value& value) {
534 auto it = items_map_.find(key);
535 if (it != items_map_.end()) {
536 cache_size_ -= it->second->second->capacity();
537 items_list_.erase(it->second);
538 items_map_.erase(it);
539 }
540 EnsureSpace(value->capacity());
541 cache_size_ += value->capacity();
542 items_list_.push_front(key_value_pair_t(key, value));
543 items_map_[key] = items_list_.begin();
544 }
545
EvictLRUItem()546 bool LRUCache::EvictLRUItem() {
547 if (items_list_.empty()) {
548 return false;
549 }
550 const auto last = items_list_.back();
551 cache_size_ -= last.second->capacity();
552 // Only write puffs large enough to disk, as disk writes have latency.
553 if (last.second->size() > 16 * 1024) {
554 WriteToDisk(last.first, last.second);
555 }
556 items_map_.erase(last.first);
557 items_list_.pop_back();
558 return true;
559 }
560
EnsureSpace(size_t size)561 void LRUCache::EnsureSpace(size_t size) {
562 while (cache_size_ + size > max_size_) {
563 if (!EvictLRUItem()) {
564 return;
565 }
566 }
567 }
568
GetTempDir()569 const char* GetTempDir() {
570 const char* tmpdir = getenv("TMPDIR");
571 if (tmpdir != nullptr) {
572 return tmpdir;
573 }
574 return "/tmp";
575 }
576
LRUCache(size_t max_size)577 LRUCache::LRUCache(size_t max_size) : max_size_(max_size) {
578 std::error_code ec;
579 auto buffer = GetTempDir() + std::string("/lru.XXXXXX");
580 if (ec) {
581 LOG(ERROR) << "Failed to get temp directory for LRU cache " << ec.message()
582 << " " << ec.value();
583 return;
584 }
585 const char* dirname = mkdtemp(buffer.data());
586 if (dirname == nullptr) {
587 LOG(ERROR) << "Failed to mkdtemp " << buffer;
588 return;
589 }
590
591 tmpdir_ = dirname;
592 }
593
594 } // namespace puffin
595