├── README.md ├── .gitignore ├── LICENSE.md └── include └── tar └── tar.hpp /README.md: -------------------------------------------------------------------------------- 1 | # tar 2 | Simple header only C++ classes to read and write tar files 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /include/tar/tar.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (c) 2013-2018 Benjamin Buch 3 | // 4 | // https://github.com/bebuch/tar 5 | // 6 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 7 | // file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 8 | //----------------------------------------------------------------------------- 9 | #ifndef _tar__tar__hpp_INCLUDED_ 10 | #define _tar__tar__hpp_INCLUDED_ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | 31 | namespace tar{ 32 | 33 | 34 | namespace impl{ namespace tar{ 35 | 36 | 37 | using index_t = std::string::difference_type; 38 | 39 | template < index_t ... I > 40 | using index_sequence_t = std::integer_sequence< index_t, I ... >; 41 | 42 | using field_size_t = index_sequence_t< 43 | 100, 8, 8, 8, 12, 12, 8, 1, 100, 6, 2, 32, 32, 8, 8, 155, 12 44 | >; 45 | 46 | 47 | template < typename IntegerSequence, index_t X > 48 | struct get_integer; 49 | 50 | template < index_t H, index_t ... I, index_t X > 51 | struct get_integer< index_sequence_t< H, I ... >, X >{ 52 | static constexpr index_t value = 53 | get_integer< index_sequence_t< I ... >, X - 1 >::value; 54 | }; 55 | 56 | template < index_t H, index_t ... I > 57 | struct get_integer< index_sequence_t< H, I ... >, index_t(0) >{ 58 | static constexpr index_t value = H; 59 | }; 60 | 61 | template < index_t X > 62 | struct field_size{ 63 | static constexpr index_t value = 64 | get_integer< field_size_t, X >::value; 65 | }; 66 | 67 | 68 | template < typename IntegerSequence, index_t X > 69 | struct get_integer_start; 70 | 71 | template < index_t H, index_t ... I, index_t X > 72 | struct get_integer_start< index_sequence_t< H, I ... >, X >{ 73 | static constexpr index_t value = 74 | get_integer_start< index_sequence_t< I ... >, X - 1 >::value + 75 | H; 76 | }; 77 | 78 | template < index_t H, index_t ... I > 79 | struct get_integer_start< index_sequence_t< H, I ... >, index_t(0) >{ 80 | static constexpr index_t value = 0; 81 | }; 82 | 83 | template < index_t X > 84 | struct field_start{ 85 | static constexpr index_t value = 86 | get_integer_start< field_size_t, X >::value; 87 | }; 88 | 89 | 90 | struct field_name{ 91 | enum values{ 92 | name, 93 | mode, 94 | uid, 95 | gid, 96 | size, 97 | mtime, 98 | checksum, 99 | typeflag, 100 | linkname, 101 | magic, 102 | version, 103 | uname, 104 | gname, 105 | devmajor, 106 | devminor, 107 | prefix, 108 | pad 109 | }; 110 | }; 111 | 112 | 113 | template < index_t FieldName, typename Container > 114 | void write( 115 | std::array< char, 512 >& buffer, 116 | Container const& container 117 | ){ 118 | static constexpr auto start = field_start< FieldName >::value; 119 | static constexpr auto size = field_size< FieldName >::value; 120 | 121 | auto const begin = container.begin(); 122 | auto const end = container.end(); 123 | 124 | if(end - begin > size){ 125 | throw std::runtime_error(io_tools::make_string( 126 | "Tar: field data to long: [", FieldName, "] is: ", 127 | end - begin, ", max: ", size 128 | )); 129 | } 130 | 131 | std::copy(begin, end, buffer.begin() + start); 132 | std::fill( 133 | buffer.begin() + start + (end - begin), 134 | buffer.begin() + start + size, 0 135 | ); 136 | } 137 | 138 | 139 | template < index_t FieldName > 140 | std::string read(std::array< char, 512 > const& buffer){ 141 | static constexpr auto start = field_start< FieldName >::value; 142 | static constexpr auto size = field_size< FieldName >::value; 143 | 144 | return std::string( 145 | buffer.begin() + start, 146 | buffer.begin() + start + size 147 | ); 148 | } 149 | 150 | constexpr std::array< char, field_size< field_name::checksum >::value > 151 | empty_checksum{{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}}; 152 | 153 | constexpr std::array< char, 5 > magic{{'u', 's', 't', 'a', 'r'}}; 154 | constexpr std::array< char, 6 > mode{{'0', '0', '0', '6', '4', '4'}}; 155 | constexpr std::array< char, 1 > typeflag{{'0'}}; 156 | 157 | inline std::string calc_checksum(std::array< char, 512 > buffer){ 158 | write< field_name::checksum >(buffer, empty_checksum); 159 | 160 | unsigned sum = 0; 161 | for(unsigned i: buffer) sum += i; 162 | 163 | std::ostringstream os; 164 | os << std::oct << std::setfill('0') << std::setw(6) << sum << '\0' 165 | << ' '; 166 | 167 | return os.str(); 168 | } 169 | 170 | inline std::array< char, 512 > make_posix_header( 171 | std::string const& name, 172 | std::size_t size 173 | ){ 174 | std::array< char, 512 > buffer{}; 175 | 176 | std::ostringstream os; 177 | os << std::oct << std::setfill('0') << std::setw(11) 178 | << std::time(nullptr); 179 | std::string mtime = os.str(); 180 | 181 | write< field_name::magic >(buffer, magic); 182 | write< field_name::mode >(buffer, mode); 183 | write< field_name::mtime >(buffer, mtime); 184 | write< field_name::typeflag >(buffer, typeflag); 185 | 186 | 187 | // Set filename 188 | if(name.size() == 0){ 189 | throw std::runtime_error("Tar: filename is empty"); 190 | } 191 | 192 | if(name.size() >= 100){ 193 | throw std::runtime_error( 194 | "Tar: filename larger than 99 charakters" 195 | ); 196 | } 197 | 198 | write< field_name::name >(buffer, name); 199 | 200 | 201 | // Set size 202 | os.str(""); 203 | os << std::oct << std::setfill('0') << std::setw(11) << size; 204 | write< field_name::size >(buffer, os.str()); 205 | 206 | 207 | // Set checksum 208 | write< field_name::checksum >(buffer, calc_checksum(buffer)); 209 | 210 | 211 | return buffer; 212 | } 213 | 214 | inline std::string cut_null(std::string const& data){ 215 | return data.substr(0, data.find('\0')); 216 | } 217 | 218 | inline std::tuple< std::string, std::size_t > read_posix_header( 219 | std::array< char, 512 > const& buffer 220 | ){ 221 | auto const checksum = read< field_name::checksum >(buffer); 222 | auto const magic = cut_null(read< field_name::magic >(buffer)); 223 | auto const size = read< field_name::size >(buffer); 224 | auto const filename = cut_null(read< field_name::name >(buffer)); 225 | 226 | if(magic != "ustar"){ 227 | throw std::runtime_error( 228 | "Tar: loaded file without magic 'ustar', magic is: '" + 229 | io_tools::mask_non_print(magic) + "'" 230 | ); 231 | } 232 | 233 | if(checksum != calc_checksum(buffer)){ 234 | throw std::runtime_error( 235 | "Tar: loaded file with wrong checksum" 236 | ); 237 | } 238 | 239 | return std::make_tuple( 240 | std::move(filename), 241 | static_cast< std::size_t >(std::stol(size, 0, 8)) 242 | ); 243 | } 244 | 245 | 246 | } } 247 | 248 | 249 | /// \brief Write a simple tar file 250 | class tar_writer{ 251 | public: 252 | tar_writer(std::string const& filename): 253 | outfile_(new std::ofstream( 254 | filename.c_str(), 255 | std::ios_base::out | std::ios_base::binary 256 | )), 257 | out_(*outfile_) {} 258 | 259 | tar_writer(std::ostream& out): 260 | out_(out) {} 261 | 262 | ~tar_writer(){ 263 | static char const dummy[1024] = {0}; 264 | out_.write(dummy, 1024); 265 | } 266 | 267 | void write( 268 | std::string const& filename, 269 | char const* content, 270 | std::size_t size 271 | ){ 272 | write(filename, [&](std::ostream& os){ 273 | os.write(content, size); 274 | }, size); 275 | } 276 | 277 | void write(std::string const& filename, std::string const& content){ 278 | write(filename, content.data(), content.size()); 279 | } 280 | 281 | void write( 282 | std::string const& filename, 283 | std::function< void(std::ostream&) > const& writer 284 | ){ 285 | std::ostringstream os(std::ios_base::out | std::ios_base::binary); 286 | writer(os); 287 | write(filename, os.str()); 288 | } 289 | 290 | void write( 291 | std::string const& filename, 292 | std::function< void(std::ostream&) > const& writer, 293 | std::size_t size 294 | ){ 295 | if(!filenames_.emplace(filename).second){ 296 | throw std::runtime_error( 297 | "Duplicate filename in tar-file: " + filename 298 | ); 299 | } 300 | 301 | auto const header = impl::tar::make_posix_header(filename, size); 302 | 303 | std::size_t end_record_bytes = (512 - (size % 512)) % 512; 304 | std::vector< char > buffer(end_record_bytes); 305 | 306 | auto start = out_.tellp(); 307 | out_.write(header.data(), header.size()); 308 | 309 | writer(out_); 310 | auto wrote_size = 311 | static_cast< std::size_t >(out_.tellp() - start) - 512; 312 | if(wrote_size != size){ 313 | out_.seekp(start); 314 | throw std::runtime_error(io_tools::make_string( 315 | "While writing '", filename, 316 | "' to tar-file: Writer function wrote ", wrote_size, 317 | " bytes, but ", size, " where expected" 318 | )); 319 | } 320 | 321 | out_.write(buffer.data(), buffer.size()); 322 | } 323 | 324 | private: 325 | /// \brief Output file, if the filename constructor have been called 326 | std::unique_ptr< std::ofstream > outfile_; 327 | 328 | /// \brief Output stream 329 | std::ostream& out_; 330 | 331 | /// \brief No filename duplicates 332 | std::set< std::string > filenames_; 333 | }; 334 | 335 | 336 | /// \brief Write a simple tar file 337 | class tar_reader{ 338 | public: 339 | tar_reader(std::string const& filename): 340 | isptr_(std::make_unique< std::ifstream >( 341 | filename.c_str(), std::ios_base::in | std::ios_base::binary 342 | )), 343 | is_(*isptr_.get()) 344 | { 345 | init(); 346 | } 347 | 348 | tar_reader(std::istream& is): 349 | is_(is) 350 | { 351 | init(); 352 | } 353 | 354 | std::istream& get(std::string const& filename){ 355 | auto iter = files_.find(filename); 356 | if(iter == files_.end()){ 357 | throw std::runtime_error( 358 | "Filename-entry not fount in tar-file: " + filename 359 | ); 360 | } 361 | 362 | iter->second.seekg(0); 363 | return iter->second; 364 | } 365 | 366 | 367 | private: 368 | void init(){ 369 | static constexpr std::array< char, 512 > empty_buffer{}; 370 | 371 | std::array< char, 512 > buffer; 372 | while(is_){ 373 | is_.read(buffer.data(), 512); 374 | 375 | if(buffer == empty_buffer){ 376 | is_.read(buffer.data(), 512); 377 | if(buffer != empty_buffer || !is_){ 378 | throw std::runtime_error("Corrupt tar-file."); 379 | } 380 | break; 381 | } 382 | 383 | std::string filename; 384 | std::size_t size; 385 | std::tie(filename, size) = 386 | impl::tar::read_posix_header(buffer); 387 | 388 | auto result = files_.emplace( 389 | std::piecewise_construct, 390 | std::forward_as_tuple(filename), 391 | std::forward_as_tuple(is_.rdbuf(), is_.tellg(), size) 392 | ); 393 | if(!result.second){ 394 | throw std::runtime_error( 395 | "Duplicate filename-entry while reading tar-file: " + 396 | filename 397 | ); 398 | } 399 | 400 | std::streampos file_size_in_tar = 401 | size + (512 - (size % 512)) % 512; 402 | is_.seekg(is_.tellg() + file_size_in_tar); 403 | 404 | 405 | if(!is_){ 406 | throw std::runtime_error( 407 | "Tar filename-entry with illegal size: " + filename 408 | ); 409 | } 410 | } 411 | } 412 | 413 | /// \brief Stream if read via filename 414 | std::unique_ptr< std::ifstream > isptr_; 415 | 416 | /// \brief Input stream of the tar-file 417 | std::istream& is_; 418 | 419 | /// \brief Map of filenames and contents 420 | std::map< std::string, io_tools::isubstream > files_; 421 | }; 422 | 423 | 424 | } 425 | 426 | 427 | #endif 428 | --------------------------------------------------------------------------------