1 #include "fileimage.hpp"
3 #include "fileimage-patch.hpp"
7 #include "directory.hpp"
14 std::map
<std::string
, std::pair
<time_t, std::string
>> cached_entries
;
16 mutex_class
& global_queue_mutex()
22 uint64_t calculate_headersize(uint64_t f
, uint64_t h
)
25 if(f
% (2 * h
) == h
) return h
;
29 void* thread_trampoline(hash
* h
)
35 std::string
lookup_cache(const std::string
& filename
, uint64_t prefixlen
)
37 std::string cache
= filename
+ ".sha256";
38 if(prefixlen
) cache
+= (stringfmt() << "-" << prefixlen
).str();
39 time_t filetime
= file_get_mtime(filename
);
40 if(cached_entries
.count(cache
)) {
41 //Found the cache entry...
42 if(cached_entries
[cache
].first
== filetime
)
43 return cached_entries
[cache
].second
;
46 unlink(cache
.c_str());
47 cached_entries
.erase(cache
);
52 std::string cached_hash
;
55 std::ifstream
in(cache
);
60 std::getline(in
, tmp
);
61 std::istringstream
_in(tmp
);
63 std::getline(in
, cached_hash
);
65 if(rfiletime
== filetime
) {
66 cached_entries
[cache
] = std::make_pair(rfiletime
, cached_hash
);
69 unlink(cache
.c_str());
70 cached_entries
.erase(cache
);
76 void store_cache(const std::string
& filename
, uint64_t prefixlen
, const std::string
& value
)
78 std::string cache
= filename
+ ".sha256";
79 if(prefixlen
) cache
+= (stringfmt() << "-" << prefixlen
).str();
80 time_t filetime
= file_get_mtime(filename
);
81 std::ofstream
out(cache
);
82 cached_entries
[cache
] = std::make_pair(filetime
, value
);
85 out
<< filetime
<< std::endl
;
86 out
<< value
<< std::endl
;
90 uint64_t get_file_size(const std::string
& filename
)
92 uintmax_t size
= file_get_size(filename
);
93 if(size
== static_cast<uintmax_t>(-1))
107 hashval::hashval(const std::string
& _value
, uint64_t _prefix
)
117 hashval::hashval(hash
& h
, unsigned id
)
119 umutex_class
h2(global_queue_mutex());
129 umutex_class
h2(global_queue_mutex());
130 umutex_class
h(mutex
);
132 hasher
->unlink(*this);
135 bool hashval::ready() const
137 umutex_class
h(mutex
);
141 std::string
hashval::read() const
143 umutex_class
h(mutex
);
147 throw std::runtime_error(error
);
151 uint64_t hashval::prefix() const
153 umutex_class
h(mutex
);
157 throw std::runtime_error(error
);
161 hashval::hashval(const hashval
& f
)
163 umutex_class
h2(global_queue_mutex());
164 umutex_class
h(f
.mutex
);
165 is_ready
= f
.is_ready
;
172 if(!is_ready
&& hasher
)
176 hashval
& hashval::operator=(const hashval
& f
)
180 umutex_class
h2(global_queue_mutex());
181 if((size_t)this < (size_t)&f
) {
189 if(!is_ready
&& hasher
)
190 hasher
->unlink(*this);
191 is_ready
= f
.is_ready
;
198 if(!is_ready
&& hasher
)
204 void hashval::resolve(unsigned id
, const std::string
& hash
, uint64_t _prefix
)
206 umutex_class
h(mutex
);
207 hasher
->unlink(*this);
213 condition
.notify_all();
216 void hashval::resolve_error(unsigned id
, const std::string
& err
)
218 umutex_class
h(mutex
);
219 hasher
->unlink(*this);
225 condition
.notify_all();
228 void hash::link(hashval
& future
)
230 //We assume caller holds global queue lock.
232 umutex_class
h(mutex
);
233 unsigned cbid
= future
.cbid
;
238 future
.prev
= last_future
;
241 last_future
->next
= &future
;
242 last_future
= &future
;
244 first_future
= &future
;
247 void hash::unlink(hashval
& future
)
249 //We assume caller holds global queue lock.
251 umutex_class
h(mutex
);
252 unsigned cbid
= future
.cbid
;
257 if(&future
== first_future
)
258 first_future
= future
.next
;
259 if(&future
== last_future
)
260 last_future
= future
.prev
;
262 future
.prev
->next
= future
.next
;
264 future
.next
->prev
= future
.prev
;
267 hashval
hash::operator()(const std::string
& filename
, uint64_t prefixlen
)
270 j
.filename
= filename
;
271 j
.prefix
= prefixlen
;
272 j
.size
= get_file_size(filename
);
273 j
.cbid
= next_cbid
++;
275 hashval
future(*this, j
.cbid
);
277 umutex_class
h(mutex
);
278 total_work
+= j
.size
;
280 condition
.notify_all();
284 hashval
hash::operator()(const std::string
& filename
, std::function
<uint64_t(uint64_t)> prefixlen
)
287 j
.filename
= filename
;
288 j
.size
= get_file_size(filename
);
289 j
.prefix
= prefixlen(j
.size
);
290 j
.cbid
= next_cbid
++;
292 hashval
future(*this, j
.cbid
);
294 umutex_class
h(mutex
);
295 total_work
+= j
.size
;
297 condition
.notify_all();
301 void hash::set_callback(std::function
<void(uint64_t, uint64_t)> cb
)
303 umutex_class
h(mutex
);
315 progresscb
= [](uint64_t x
, uint64_t y
) -> void {};
316 hash_thread
= new thread_class(thread_trampoline
, this);
322 umutex_class
h(mutex
);
324 condition
.notify_all();
328 umutex_class
h2(global_queue_mutex());
330 first_future
->resolve_error(first_future
->cbid
, "Hasher deleted");
333 void hash::entrypoint()
337 //Wait for work or quit signal.
339 umutex_class
h(mutex
);
340 while(!quitting
&& queue
.empty()) {
347 current_job
= queue
.begin();
351 uint64_t progress
= 0;
352 std::string cached_hash
;
354 cached_hash
= lookup_cache(current_job
->filename
, current_job
->prefix
);
355 if(cached_hash
!= "") {
356 umutex_class
h2(global_queue_mutex());
357 for(hashval
* fut
= first_future
; fut
!= NULL
; fut
= fut
->next
)
358 fut
->resolve(current_job
->cbid
, cached_hash
, current_job
->prefix
);
361 fp
= fopen(current_job
->filename
.c_str(), "rb");
363 umutex_class
h2(global_queue_mutex());
364 for(hashval
* fut
= first_future
; fut
!= NULL
; fut
= fut
->next
)
365 fut
->resolve_error(current_job
->cbid
, "Can't open file");
368 uint64_t toskip
= current_job
->prefix
;
369 while(!feof(fp
) && !ferror(fp
)) {
371 umutex_class
h(mutex
);
372 if(!current_job
->interested
)
373 goto finished
; //Aborted.
375 unsigned char buf
[16384];
377 size_t s
= fread(buf
, 1, sizeof(buf
), fp
);
379 //The first current_job->prefix bytes need to be skipped.
380 offset
= min(toskip
, (uint64_t)s
);
382 if(s
> offset
) hash
.write(buf
+ offset
, s
- offset
);
383 send_callback(progress
);
386 umutex_class
h2(global_queue_mutex());
387 for(hashval
* fut
= first_future
; fut
!= NULL
; fut
= fut
->next
)
388 fut
->resolve_error(current_job
->cbid
, "Can't read file");
390 std::string hval
= hash
.read();
391 umutex_class
h2(global_queue_mutex());
392 for(hashval
* fut
= first_future
; fut
!= NULL
; fut
= fut
->next
)
393 fut
->resolve(current_job
->cbid
, hval
, current_job
->prefix
);
394 store_cache(current_job
->filename
, current_job
->prefix
, hval
);
399 //Okay, this work item is complete.
401 umutex_class
h(mutex
);
402 total_work
-= current_job
->size
;
403 queue
.erase(current_job
);
409 void hash::send_callback(uint64_t this_completed
)
413 umutex_class
h(mutex
);
414 if(this_completed
> total_work
)
417 amount
= total_work
- this_completed
;
419 progresscb(amount
, work_size
);
422 void hash::send_idle()
424 work_size
= 0; //Delete work when idle.
425 progresscb(0xFFFFFFFFFFFFFFFFULL
, 0);
428 image::image() throw(std::bad_alloc
)
430 type
= info::IT_NONE
;
431 sha_256
= hashval("");
435 image::image(hash
& h
, const std::string
& _filename
, const std::string
& base
,
436 const struct image::info
& info
) throw(std::bad_alloc
, std::runtime_error
)
438 if(info
.type
== info::IT_NONE
&& _filename
!= "")
439 throw std::runtime_error("Tried to load NULL image");
440 if(_filename
== "") {
442 type
= info::IT_NONE
;
443 sha_256
= hashval("");
448 std::string xfilename
= _filename
;
449 #if defined(_WIN32) || defined(_WIN64)
450 const char* split
= "/\\";
452 const char* split
= "/";
454 size_t s1
= xfilename
.find_last_of(split
);
455 size_t s2
= xfilename
.find_last_of(".");
456 if(s1
< xfilename
.length()) s1
= s1
+ 1; else s1
= 0;
457 if(s2
<= s1
|| s2
>= xfilename
.length()) s2
= xfilename
.length();
458 namehint
= xfilename
.substr(s1
, s2
- s1
);
460 //Load markups and memory images.
461 if(info
.type
== info::IT_MEMORY
|| info
.type
== info::IT_MARKUP
) {
462 unsigned headered
= 0;
463 filename
= zip::resolverel(_filename
, base
);
466 data
.reset(new std::vector
<char>(zip::readrel(_filename
, base
)));
467 headered
= (info
.type
== info::IT_MEMORY
) ? calculate_headersize(data
->size(), info
.headersize
) : 0;
468 if(data
->size() >= headered
) {
470 memmove(&(*data
)[0], &(*data
)[headered
], data
->size() - headered
);
471 data
->resize(data
->size() - headered
);
477 sha_256
= hashval(sha256::hash(*data
), headered
);
478 if(info
.type
== info::IT_MARKUP
) {
479 size_t osize
= data
->size();
480 data
->resize(osize
+ 1);
486 if(info
.type
== info::IT_FILE
) {
487 filename
= zip::resolverel(_filename
, base
);
488 filename
= get_absolute_path(filename
);
489 type
= info::IT_FILE
;
490 data
.reset(new std::vector
<char>(filename
.begin(), filename
.end()));
492 sha_256
= h(filename
);
495 throw std::runtime_error("Unknown image type");
498 void image::patch(const std::vector
<char>& patch
, int32_t offset
) throw(std::bad_alloc
, std::runtime_error
)
500 if(type
== info::IT_NONE
)
501 throw std::runtime_error("Not an image");
502 if(type
!= info::IT_MEMORY
&& type
!= info::IT_MARKUP
)
503 throw std::runtime_error("File images can't be patched on the fly");
505 std::vector
<char> data2
= *data
;
506 if(type
== info::IT_MARKUP
)
507 data2
.resize(data2
.size() - 1);
508 data2
= ::fileimage::patch(data2
, patch
, offset
);
509 //Mark the slot as valid and update hash.
510 std::string new_sha256
= sha256::hash(data2
);
511 if(type
== info::IT_MARKUP
) {
512 size_t osize
= data2
.size();
513 data2
.resize(osize
+ 1);
516 data
.reset(new std::vector
<char>(data2
));
517 sha_256
= hashval(new_sha256
);
523 std::function
<uint64_t(uint64_t)> std_headersize_fn(uint64_t hdrsize
)
525 uint64_t h
= hdrsize
;
526 return ([h
](uint64_t x
) -> uint64_t { return calculate_headersize(x
, h
); });