xref: /aosp_15_r20/external/puffin/src/puffin_stream.cc (revision 07fb1d065b7cfb4729786fadd42a612532d2f466)
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