3 Copyright (c) 2007, Arvid Norberg, Daniel Wallin
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
33 #include "libtorrent/pch.hpp"
35 #include "libtorrent/storage.hpp"
36 #include "libtorrent/size_type.hpp"
37 #include "libtorrent/file.hpp"
41 #pragma warning(push, 1)
44 #include <boost/iostreams/device/mapped_file.hpp>
45 #include <boost/filesystem/path.hpp>
46 #include <boost/filesystem/operations.hpp>
47 #include <boost/utility.hpp>
48 #include <boost/bind.hpp>
49 #include <boost/lexical_cast.hpp>
50 #if BOOST_VERSION >= 103500
51 #include <boost/system/error_code.hpp>
54 #include <boost/version.hpp>
60 using boost::iostreams::mapped_file
;
61 using boost::iostreams::mapped_file_params
;
65 namespace fs
= boost::filesystem
;
66 #if BOOST_VERSION >= 103500
67 typedef boost::system::error_code ec_t
;
69 typedef error_code ec_t
;
72 struct mapped_file_pool
74 mapped_file_pool(int size
= 40): m_size(size
) {}
78 enum { view_size
= 100 * 1024 * 1024 };
83 file_entry() : key(0), references(0) {}
84 bool open(fs::path
const& path
, std::ios::openmode openmode
85 , size_type start
, size_type size
, void* key_
, size_type file_size
= 0)
91 fs::file_status st
= fs::status(path
, ec
);
92 TORRENT_ASSERT(!fs::exists(st
));
96 last_use
= time_now();
97 params
.path
= path
.string();
98 params
.mode
= openmode
;
99 params
.offset
= start
;
100 params
.length
= size
;
101 params
.new_file_size
= file_size
;
103 return file
.is_open();
105 mapped_file_params params
;
112 typedef std::list
<file_entry
> files_t
;
119 explicit file_view(file_entry
* e
): m_entry(e
) { ++m_entry
->references
; }
120 file_view(): m_entry(0) {}
121 file_view(file_view
const& f
): m_entry(f
.m_entry
)
122 { if (m_entry
) ++m_entry
->references
; }
125 TORRENT_ASSERT(m_entry
== 0 || m_entry
->references
> 0);
126 if (m_entry
) --m_entry
->references
;
128 file_view
& operator=(file_view
const& v
)
130 TORRENT_ASSERT(m_entry
== 0 || m_entry
->references
> 0);
131 if (m_entry
) --m_entry
->references
;
133 if (m_entry
) ++m_entry
->references
;
137 bool valid() const { return m_entry
&& m_entry
->file
.const_data(); }
141 TORRENT_ASSERT(m_entry
);
142 return m_entry
->file
.data();
145 char const* const_addr() const
147 TORRENT_ASSERT(m_entry
);
148 return m_entry
->file
.const_data();
151 size_type
offset() const
153 TORRENT_ASSERT(m_entry
);
154 return m_entry
->params
.offset
;
157 size_type
size() const
159 TORRENT_ASSERT(m_entry
);
160 return m_entry
->params
.length
;
167 file_view
open_file(fs::path
const& p
, std::ios::openmode mode
168 , size_type offset
, size_type length
, void* key
169 , size_type file_size
)
171 TORRENT_ASSERT(file_size
> 0);
172 files_t::iterator min
= m_files
.end();
173 for (std::list
<file_entry
>::iterator i
= m_files
.begin()
174 , end(m_files
.end()); i
!= end
; ++i
)
176 if (i
->params
.path
== p
.string()
177 && i
->params
.offset
<= offset
178 && i
->params
.offset
+ i
->params
.length
>= offset
+ length
)
180 if (i
->key
!= key
) return file_view();
181 if ((mode
& std::ios::out
) && (i
->params
.mode
& std::ios::out
) == 0)
183 TORRENT_ASSERT(i
->references
== 0);
189 i
->last_use
= time_now();
190 return file_view(&(*i
));
192 if ((min
== m_files
.end() || i
->last_use
< min
->last_use
)
193 && i
->references
== 0)
199 if (int(m_files
.size()) >= m_size
&& min
!= m_files
.end())
201 TORRENT_ASSERT(min
->references
== 0);
206 size_type start
= (offset
/ view_size
) * view_size
;
207 TORRENT_ASSERT(start
+ view_size
>= offset
+ length
);
209 #if BOOST_VERSION < 103500
210 fs::system_error_type ec
;
214 fs::file_status st
= fs::status(p
, ec
);
216 m_files
.push_back(file_entry());
220 ret
= m_files
.back().open(p
, mode
| std::ios::out
, start
, view_size
, key
, file_size
);
224 if (is_directory(st
)) return file_view();
225 size_type s
= fs::file_size(p
);
227 // TODO: SetFileSize()
228 if (s
< file_size
) {}
230 if (s
< file_size
) truncate(p
.string().c_str(), file_size
);
232 ret
= m_files
.back().open(p
, mode
, start
, view_size
, key
);
238 m_files
.erase(boost::prior(m_files
.end()));
241 return file_view(&m_files
.back());
244 void release(void* key
)
246 for (std::list
<file_entry
>::iterator i
= m_files
.begin();
247 !m_files
.empty() && i
!= m_files
.end();)
251 TORRENT_ASSERT(i
->references
== 0);
263 struct mapped_storage
: storage_interface
265 mapped_storage(file_storage
const& fs
, fs::path save_path
)
267 , m_save_path(save_path
)
270 bool initialize(bool allocate_files
) { return false; }
272 int read(char* buf
, int slot
, int offset
, int size
)
274 TORRENT_ASSERT(buf
!= 0);
275 TORRENT_ASSERT(slot
>= 0 && slot
< m_files
.num_pieces());
276 TORRENT_ASSERT(offset
>= 0);
277 TORRENT_ASSERT(offset
< m_files
.piece_size(slot
));
278 TORRENT_ASSERT(size
> 0);
280 size_type result
= -1;
285 std::vector
<file_slice
> slices
286 = files().map_block(slot
, offset
, size
);
287 TORRENT_ASSERT(!slices
.empty());
289 size_type start
= slot
* (size_type
)m_files
.piece_length() + offset
;
290 TORRENT_ASSERT(start
+ size
<= m_files
.total_size());
292 // find the file iterator and file offset
293 size_type file_offset
= start
;
294 std::vector
<file_entry
>::const_iterator file_iter
;
296 for (file_iter
= files().begin();;)
298 if (file_offset
< file_iter
->size
)
301 file_offset
-= file_iter
->size
;
305 TORRENT_ASSERT(file_iter
->size
> 0);
306 mapped_file_pool::file_view view
= m_pool
.open_file(
307 m_save_path
/ file_iter
->path
, std::ios::in
308 , file_offset
+ file_iter
->file_base
, size
, this
309 , file_iter
->size
+ file_iter
->file_base
);
313 set_error((m_save_path
/ file_iter
->path
).string(), "failed to open file for reading");
316 TORRENT_ASSERT(view
.const_addr() != 0);
318 int left_to_read
= size
;
320 result
= left_to_read
;
324 while (left_to_read
> 0)
326 int read_bytes
= left_to_read
;
327 if (file_offset
+ read_bytes
> file_iter
->size
)
328 read_bytes
= static_cast<int>(file_iter
->size
- file_offset
);
333 TORRENT_ASSERT(int(slices
.size()) > counter
);
334 size_type slice_size
= slices
[counter
].size
;
335 TORRENT_ASSERT(slice_size
== read_bytes
);
336 TORRENT_ASSERT(files().at(slices
[counter
].file_index
).path
340 TORRENT_ASSERT(file_offset
+ file_iter
->file_base
>= view
.offset());
341 TORRENT_ASSERT(view
.const_addr() != 0);
342 std::memcpy(buf
+ buf_pos
343 , view
.const_addr() + (file_offset
+ file_iter
->file_base
- view
.offset())
346 left_to_read
-= read_bytes
;
347 buf_pos
+= read_bytes
;
348 TORRENT_ASSERT(buf_pos
>= 0);
349 file_offset
+= read_bytes
;
352 if (left_to_read
> 0)
356 while (file_iter
!= files().end() && file_iter
->size
== 0)
360 // empty files are not returned by map_block, so if
361 // this file was empty, don't increment the slice counter
362 if (read_bytes
> 0) ++counter
;
364 fs::path path
= m_save_path
/ file_iter
->path
;
368 view
= m_pool
.open_file(path
, std::ios::in
, file_offset
+ file_iter
->file_base
370 , file_iter
->size
+ file_iter
->file_base
);
374 set_error((m_save_path
/ file_iter
->path
).string(), "failed to open for reading");
377 TORRENT_ASSERT(view
.const_addr() != 0);
381 catch (std::exception
& e
)
383 set_error("", e
.what());
390 int write(const char* buf
, int slot
, int offset
, int size
)
392 TORRENT_ASSERT(buf
!= 0);
393 TORRENT_ASSERT(slot
>= 0 && slot
< m_files
.num_pieces());
394 TORRENT_ASSERT(offset
>= 0);
395 TORRENT_ASSERT(offset
< m_files
.piece_size(slot
));
396 TORRENT_ASSERT(size
> 0);
399 std::vector
<file_slice
> slices
400 = files().map_block(slot
, offset
, size
);
401 TORRENT_ASSERT(!slices
.empty());
403 size_type start
= slot
* (size_type
)m_files
.piece_length() + offset
;
404 TORRENT_ASSERT(start
+ size
<= m_files
.total_size());
406 // find the file iterator and file offset
407 size_type file_offset
= start
;
408 std::vector
<file_entry
>::const_iterator file_iter
;
410 for (file_iter
= files().begin();;)
412 if (file_offset
< file_iter
->size
)
415 file_offset
-= file_iter
->size
;
419 TORRENT_ASSERT(file_iter
->size
> 0);
423 mapped_file_pool::file_view view
= m_pool
.open_file(
424 m_save_path
/ file_iter
->path
, std::ios::in
| std::ios::out
425 , file_offset
+ file_iter
->file_base
, size
, this
426 , file_iter
->size
+ file_iter
->file_base
);
430 set_error((m_save_path
/ file_iter
->path
).string(), "failed to open file for writing");
433 TORRENT_ASSERT(view
.addr() != 0);
435 int left_to_write
= size
;
440 while (left_to_write
> 0)
442 int write_bytes
= left_to_write
;
443 if (file_offset
+ write_bytes
> file_iter
->size
)
444 write_bytes
= static_cast<int>(file_iter
->size
- file_offset
);
449 TORRENT_ASSERT(int(slices
.size()) > counter
);
450 size_type slice_size
= slices
[counter
].size
;
451 TORRENT_ASSERT(slice_size
== write_bytes
);
452 TORRENT_ASSERT(files().at(slices
[counter
].file_index
).path
456 TORRENT_ASSERT(file_offset
+ file_iter
->file_base
>= view
.offset());
457 TORRENT_ASSERT(view
.addr() != 0);
458 std::memcpy(view
.addr() + (file_offset
+ file_iter
->file_base
- view
.offset())
462 left_to_write
-= write_bytes
;
463 buf_pos
+= write_bytes
;
464 TORRENT_ASSERT(buf_pos
>= 0);
465 file_offset
+= write_bytes
;
468 if (left_to_write
> 0)
471 while (file_iter
!= files().end() && file_iter
->size
== 0)
474 // empty files are not returned by map_block, so if
475 // this file was empty, don't increment the slice counter
476 if (write_bytes
> 0) ++counter
;
478 fs::path path
= m_save_path
/ file_iter
->path
;
481 view
= m_pool
.open_file(path
, std::ios::in
| std::ios::out
482 , file_offset
+ file_iter
->file_base
, left_to_write
, this
483 , file_iter
->size
+ file_iter
->file_base
);
487 set_error((m_save_path
/ file_iter
->path
).string(), "failed to open file for reading");
490 TORRENT_ASSERT(view
.addr() != 0);
494 catch (std::exception
& e
)
496 set_error((m_save_path
/ file_iter
->path
).string(), e
.what());
502 bool move_storage(fs::path save_path
)
504 #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400
512 save_path
= complete(save_path
);
514 #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400
515 std::wstring
wsave_path(safe_convert(save_path
.native_file_string()));
516 if (!exists_win(save_path
))
517 CreateDirectory(wsave_path
.c_str(), 0);
518 else if ((GetFileAttributes(wsave_path
.c_str()) & FILE_ATTRIBUTE_DIRECTORY
) == 0)
520 #elif defined(_WIN32) && defined(UNICODE)
521 fs::wpath wp
= safe_convert(save_path
.string());
523 create_directory(wp
);
524 else if (!is_directory(wp
))
527 if (!exists(save_path
))
528 create_directory(save_path
);
529 else if (!is_directory(save_path
))
533 m_pool
.release(this);
535 #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400
536 old_path
= safe_convert((m_save_path
/ files().name()).string());
537 new_path
= safe_convert((save_path
/ files().name()).string());
539 old_path
= m_save_path
/ files().name();
540 new_path
= save_path
/ files().name();
545 #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION < 103400
546 rename_win(old_path
, new_path
);
547 rename(old_path
, new_path
);
549 rename(old_path
, new_path
);
551 m_save_path
= save_path
;
554 catch (std::exception
& e
)
557 std::cerr
<< "ERROR: " << e
.what() << std::endl
;
563 bool verify_resume_data(lazy_entry
const& rd
, std::string
& error
)
565 if (rd
.type() != lazy_entry::dict_t
)
567 error
= "invalid fastresume file (not a dictionary)";
571 std::vector
<std::pair
<size_type
, std::time_t> > file_sizes
;
572 lazy_entry
const* file_sizes_ent
= rd
.dict_find_list("file sizes");
573 if (file_sizes_ent
== 0)
575 error
= "missing or invalid 'file sizes' entry in resume data";
579 for (int i
= 0; i
< file_sizes_ent
->list_size(); ++i
)
581 lazy_entry
const* e
= file_sizes_ent
->list_at(i
);
582 if (e
->type() != lazy_entry::list_t
583 || e
->list_size() != 2
584 || e
->list_at(0)->type() != lazy_entry::int_t
585 || e
->list_at(1)->type() != lazy_entry::int_t
)
587 file_sizes
.push_back(std::pair
<size_type
, std::time_t>(
588 e
->list_int_value_at(0), std::time_t(e
->list_int_value_at(1))));
591 if (file_sizes
.empty())
593 error
= "the number of files in resume data is 0";
597 lazy_entry
const* slots
= rd
.dict_find_list("slots");
600 error
= "missing or invalid 'slots' entry in resume data";
606 if (int(slots
->list_size()) == m_files
.num_pieces())
609 for (int i
= 0; i
< slots
->list_size(); ++i
)
611 lazy_entry
const* e
= slots
->list_at(i
);
612 if (e
->list_int_value_at(i
, -1) >= 0) continue;
618 bool full_allocation_mode
= false;
619 if (rd
.dict_find_string_value("allocation") == "full")
620 full_allocation_mode
= true;
624 if (files().num_files() != (int)file_sizes
.size())
626 error
= "the number of files does not match the torrent (num: "
627 + boost::lexical_cast
<std::string
>(file_sizes
.size()) + " actual: "
628 + boost::lexical_cast
<std::string
>(files().num_files()) + ")";
632 std::vector
<std::pair
<size_type
, std::time_t> >::iterator
633 fs
= file_sizes
.begin();
634 // the resume data says we have the entire torrent
635 // make sure the file sizes are the right ones
636 for (file_storage::iterator i
= files().begin()
637 , end(files().end()); i
!= end
; ++i
, ++fs
)
639 if (i
->size
!= fs
->first
)
641 error
= "file size for '" + i
->path
.native_file_string()
642 + "' was expected to be "
643 + boost::lexical_cast
<std::string
>(i
->size
) + " bytes";
649 return match_filesizes(files(), m_save_path
, file_sizes
650 , !full_allocation_mode
, &error
);
653 bool write_resume_data(entry
& rd
) const
655 if (rd
.type() != entry::dictionary_t
)
657 set_error("", "invalid fastresume file");
660 std::vector
<std::pair
<size_type
, std::time_t> > file_sizes
661 = get_filesizes(m_files
, m_save_path
);
663 entry::list_type
& fl
= rd
["file sizes"].list();
664 for (std::vector
<std::pair
<size_type
, std::time_t> >::iterator i
665 = file_sizes
.begin(), end(file_sizes
.end()); i
!= end
; ++i
)
668 p
.push_back(entry(i
->first
));
669 p
.push_back(entry(i
->second
));
670 fl
.push_back(entry(p
));
675 bool move_slot(int src_slot
, int dst_slot
)
677 // TODO: this can be optimized by mapping both slots and do a straight memcpy
678 int piece_size
= m_files
.piece_size(dst_slot
);
679 m_scratch_buffer
.resize(piece_size
);
680 size_type ret1
= read(&m_scratch_buffer
[0], src_slot
, 0, piece_size
);
681 size_type ret2
= write(&m_scratch_buffer
[0], dst_slot
, 0, piece_size
);
682 return ret1
!= piece_size
|| ret2
!= piece_size
;
685 bool swap_slots(int slot1
, int slot2
)
687 // TODO: this can be optimized by mapping both slots and do a straight memcpy
688 // the size of the target slot is the size of the piece
689 int piece_size
= m_files
.piece_length();
690 int piece1_size
= m_files
.piece_size(slot2
);
691 int piece2_size
= m_files
.piece_size(slot1
);
692 m_scratch_buffer
.resize(piece_size
* 2);
693 size_type ret1
= read(&m_scratch_buffer
[0], slot1
, 0, piece1_size
);
694 size_type ret2
= read(&m_scratch_buffer
[piece_size
], slot2
, 0, piece2_size
);
695 size_type ret3
= write(&m_scratch_buffer
[0], slot2
, 0, piece1_size
);
696 size_type ret4
= write(&m_scratch_buffer
[piece_size
], slot1
, 0, piece2_size
);
697 return ret1
!= piece1_size
|| ret2
!= piece2_size
698 || ret3
!= piece1_size
|| ret4
!= piece2_size
;
701 bool swap_slots3(int slot1
, int slot2
, int slot3
)
703 // TODO: this can be optimized by mapping both slots and do a straight memcpy
704 // the size of the target slot is the size of the piece
705 int piece_size
= m_files
.piece_length();
706 int piece1_size
= m_files
.piece_size(slot2
);
707 int piece2_size
= m_files
.piece_size(slot3
);
708 int piece3_size
= m_files
.piece_size(slot1
);
709 m_scratch_buffer
.resize(piece_size
* 2);
710 size_type ret1
= read(&m_scratch_buffer
[0], slot1
, 0, piece1_size
);
711 size_type ret2
= read(&m_scratch_buffer
[piece_size
], slot2
, 0, piece2_size
);
712 size_type ret3
= write(&m_scratch_buffer
[0], slot2
, 0, piece1_size
);
713 size_type ret4
= read(&m_scratch_buffer
[0], slot3
, 0, piece3_size
);
714 size_type ret5
= write(&m_scratch_buffer
[piece_size
], slot3
, 0, piece2_size
);
715 size_type ret6
= write(&m_scratch_buffer
[0], slot1
, 0, piece3_size
);
716 return ret1
!= piece1_size
|| ret2
!= piece2_size
717 || ret3
!= piece1_size
|| ret4
!= piece3_size
718 || ret5
!= piece2_size
|| ret6
!= piece3_size
;
721 sha1_hash
hash_for_slot(int slot
, partial_hash
& ph
, int piece_size
)
726 int slot_size1
= piece_size
;
727 m_scratch_buffer
.resize(slot_size1
);
728 read(&m_scratch_buffer
[0], slot
, 0, slot_size1
);
730 partial
.update(&m_scratch_buffer
[0], ph
.offset
);
731 whole
.update(&m_scratch_buffer
[0], slot_size1
);
732 hasher partial_copy
= ph
.h
;
733 TORRENT_ASSERT(ph
.offset
== 0 || partial_copy
.final() == partial
.final());
735 int slot_size
= piece_size
- ph
.offset
;
738 m_scratch_buffer
.resize(slot_size
);
739 read(&m_scratch_buffer
[0], slot
, ph
.offset
, slot_size
);
740 ph
.h
.update(&m_scratch_buffer
[0], slot_size
);
743 sha1_hash ret
= ph
.h
.final();
744 TORRENT_ASSERT(ret
== whole
.final());
751 bool rename_file(int index
, std::string
const& new_filename
)
753 if (index
< 0 || index
>= m_files
.num_files()) return true;
754 fs::path old_name
= m_save_path
/ files().at(index
).path
;
755 m_pool
.release(this);
757 #if defined(_WIN32) && defined(UNICODE) && BOOST_VERSION >= 103400
758 fs::wpath old_path
= safe_convert(old_name
.string());
759 fs::wpath new_path
= safe_convert((m_save_path
/ new_filename
).string());
761 fs::path
const& old_path
= old_name
;
762 fs::path new_path
= m_save_path
/ new_filename
;
765 #ifndef BOOST_NO_EXCEPTIONS
769 rename(old_path
, new_path
);
771 { m_mapped_files
.reset(new file_storage(m_files
)); }
772 m_mapped_files
->rename_file(index
, new_filename
);
773 #ifndef BOOST_NO_EXCEPTIONS
775 catch (std::exception
& e
)
777 set_error(old_name
.string(), e
.what());
786 m_pool
.release(this);
792 // make sure we don't have the files open
793 m_pool
.release(this);
794 buffer().swap(m_scratch_buffer
);
798 std::string error_file
;
800 // delete the files from disk
801 std::set
<std::string
> directories
;
802 typedef std::set
<std::string
>::iterator iter_t
;
803 for (file_storage::iterator i
= m_files
.begin()
804 , end(m_files
.end()); i
!= end
; ++i
)
806 std::string p
= (m_save_path
/ i
->path
).string();
807 fs::path bp
= i
->path
.branch_path();
808 std::pair
<iter_t
, bool> ret
;
810 while (ret
.second
&& !bp
.empty())
812 std::pair
<iter_t
, bool> ret
= directories
.insert((m_save_path
/ bp
).string());
813 bp
= bp
.branch_path();
815 if (std::remove(p
.c_str()) != 0 && errno
!= ENOENT
)
817 error
= std::strerror(errno
);
823 // remove the directories. Reverse order to delete
824 // subdirectories first
826 for (std::set
<std::string
>::reverse_iterator i
= directories
.rbegin()
827 , end(directories
.rend()); i
!= end
; ++i
)
829 if (std::remove(i
->c_str()) != 0 && errno
!= ENOENT
)
831 error
= std::strerror(errno
);
837 if (!error
.empty()) set_error(error_file
, error
);
843 file_storage
const& files() const { return m_mapped_files
?*m_mapped_files
:m_files
; }
845 boost::scoped_ptr
<file_storage
> m_mapped_files
;
846 file_storage
const& m_files
;
847 fs::path m_save_path
;
849 // temporary storage for moving pieces
850 buffer m_scratch_buffer
;
852 static mapped_file_pool m_pool
;
855 storage_interface
* mapped_storage_constructor(file_storage
const& fs
856 , fs::path
const& path
, file_pool
& fp
)
858 return new mapped_storage(fs
, path
);
861 mapped_file_pool
mapped_storage::m_pool
;