/* Tarlz - Archiver with multimember lzip compression Copyright (C) 2013-2022 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include "tarlz.h" #include "arg_parser.h" namespace { enum { mode_string_size = 10, group_string_size = 1 + uname_l + 1 + gname_l + 1 }; // 67 void format_mode_string( const Tar_header header, char buf[mode_string_size] ) { const Typeflag typeflag = (Typeflag)header[typeflag_o]; std::memcpy( buf, "----------", mode_string_size ); switch( typeflag ) { case tf_regular: break; case tf_link: buf[0] = 'h'; break; case tf_symlink: buf[0] = 'l'; break; case tf_chardev: buf[0] = 'c'; break; case tf_blockdev: buf[0] = 'b'; break; case tf_directory: buf[0] = 'd'; break; case tf_fifo: buf[0] = 'p'; break; case tf_hiperf: buf[0] = 'C'; break; default: buf[0] = '?'; } const mode_t mode = parse_octal( header + mode_o, mode_l ); // 12 bits const bool setuid = mode & S_ISUID; const bool setgid = mode & S_ISGID; const bool sticky = mode & S_ISVTX; if( mode & S_IRUSR ) buf[1] = 'r'; if( mode & S_IWUSR ) buf[2] = 'w'; if( mode & S_IXUSR ) buf[3] = setuid ? 's' : 'x'; else if( setuid ) buf[3] = 'S'; if( mode & S_IRGRP ) buf[4] = 'r'; if( mode & S_IWGRP ) buf[5] = 'w'; if( mode & S_IXGRP ) buf[6] = setgid ? 's' : 'x'; else if( setgid ) buf[6] = 'S'; if( mode & S_IROTH ) buf[7] = 'r'; if( mode & S_IWOTH ) buf[8] = 'w'; if( mode & S_IXOTH ) buf[9] = sticky ? 't' : 'x'; else if( sticky ) buf[9] = 'T'; } int format_user_group_string( const Extended & extended, const Tar_header header, char buf[group_string_size] ) { int len; if( header[uname_o] && header[gname_o] ) len = snprintf( buf, group_string_size, " %.32s/%.32s", header + uname_o, header + gname_o ); else len = snprintf( buf, group_string_size, " %llu/%llu", extended.get_uid(), extended.get_gid() ); return len; } // return true if dir is a parent directory of name bool compare_prefix_dir( const char * const dir, const char * const name ) { int len = 0; while( dir[len] && dir[len] == name[len] ) ++len; return ( !dir[len] && len > 0 && ( dir[len-1] == '/' || name[len] == '/' ) ); } // compare two file names ignoring trailing slashes bool compare_tslash( const char * const name1, const char * const name2 ) { const char * p = name1; const char * q = name2; while( *p && *p == *q ) { ++p; ++q; } while( *p == '/' ) ++p; while( *q == '/' ) ++q; return ( !*p && !*q ); } } // end namespace bool block_is_zero( const uint8_t * const buf, const int size ) { for( int i = 0; i < size; ++i ) if( buf[i] != 0 ) return false; return true; } bool format_member_name( const Extended & extended, const Tar_header header, Resizable_buffer & rbuf, const bool long_format ) { if( long_format ) { format_mode_string( header, rbuf() ); const int group_string_len = format_user_group_string( extended, header, rbuf() + mode_string_size ); int offset = mode_string_size + group_string_len; const time_t mtime = extended.mtime().sec(); struct tm t; if( !localtime_r( &mtime, &t ) ) // if local time fails { time_t z = 0; if( !gmtime_r( &z, &t ) ) // use the UTC epoch { t.tm_year = 70; t.tm_mon = t.tm_hour = t.tm_min = 0; t.tm_mday = 1; } } const Typeflag typeflag = (Typeflag)header[typeflag_o]; const bool islink = ( typeflag == tf_link || typeflag == tf_symlink ); const char * const link_string = !islink ? "" : ( ( typeflag == tf_link ) ? " link to " : " -> " ); // print "user/group size" in a field of width 19 with 8 or more for size if( typeflag == tf_chardev || typeflag == tf_blockdev ) { const unsigned devmajor = parse_octal( header + devmajor_o, devmajor_l ); const unsigned devminor = parse_octal( header + devminor_o, devminor_l ); const int width = std::max( 1, std::max( 8, 19 - group_string_len ) - 1 - decimal_digits( devminor ) ); offset += snprintf( rbuf() + offset, rbuf.size() - offset, " %*u,%u", width, devmajor, devminor ); } else { const int width = std::max( 8, 19 - group_string_len ); offset += snprintf( rbuf() + offset, rbuf.size() - offset, " %*llu", width, extended.file_size() ); } for( int i = 0; i < 2; ++i ) // resize rbuf if not large enough { const int len = snprintf( rbuf() + offset, rbuf.size() - offset, " %4d-%02u-%02u %02u:%02u %s%s%s\n", 1900 + t.tm_year, 1 + t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, extended.path().c_str(), link_string, islink ? extended.linkpath().c_str() : "" ); if( len + offset < (int)rbuf.size() ) break; if( !rbuf.resize( len + offset + 1 ) ) return false; } } else { if( rbuf.size() < extended.path().size() + 2 && !rbuf.resize( extended.path().size() + 2 ) ) return false; snprintf( rbuf(), rbuf.size(), "%s\n", extended.path().c_str() ); } return true; } bool show_member_name( const Extended & extended, const Tar_header header, const int vlevel, Resizable_buffer & rbuf ) { if( verbosity >= vlevel ) { if( !format_member_name( extended, header, rbuf, verbosity > vlevel ) ) { show_error( mem_msg ); return false; } std::fputs( rbuf(), stdout ); std::fflush( stdout ); } return true; } bool check_skip_filename( const Cl_options & cl_opts, std::vector< char > & name_pending, const char * const filename ) { if( Exclude::excluded( filename ) ) return true; // skip excluded files bool skip = cl_opts.num_files > 0; // if no files specified, skip nothing if( skip ) // else skip all but the files (or trees) specified for( int i = 0; i < cl_opts.parser.arguments(); ++i ) if( nonempty_arg( cl_opts.parser, i ) ) { std::string removed_prefix; const char * const name = remove_leading_dotslash( cl_opts.parser.argument( i ).c_str(), &removed_prefix ); if( compare_prefix_dir( name, filename ) || compare_tslash( name, filename ) ) { print_removed_prefix( removed_prefix ); skip = false; name_pending[i] = false; break; } } return skip; } mode_t get_umask() { static mode_t mask = 0; // read once, cache the result static bool first_call = true; if( first_call ) { first_call = false; mask = umask( 0 ); umask( mask ); mask &= S_IRWXU | S_IRWXG | S_IRWXO; } return mask; } bool make_path( const std::string & name ) { const mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; unsigned end = name.size(); // first slash before last component while( end > 0 && name[end-1] == '/' ) --end; // remove trailing slashes while( end > 0 && name[end-1] != '/' ) --end; // remove last component while( end > 0 && name[end-1] == '/' ) --end; // remove more slashes unsigned index = 0; while( index < end ) { while( index < end && name[index] == '/' ) ++index; unsigned first = index; while( index < end && name[index] != '/' ) ++index; if( first < index ) { const std::string partial( name, 0, index ); struct stat st; if( lstat( partial.c_str(), &st ) == 0 ) { if( !S_ISDIR( st.st_mode ) ) { errno = ENOTDIR; return false; } } else if( mkdir( partial.c_str(), mode ) != 0 && errno != EEXIST ) return false; // if EEXIST, another thread or process created the dir } } return true; }