#define _POSIX_C_SOURCE 200809L #include /* closedir, fdopendir, readdir, rewinddir */ #include /* errno, EEXIST, ENOENT */ #include /* fstatat, mkdirat, openat, unlinkat */ #include /* strcmp, strlen */ #include /* fstat, fstatat, mkdirat */ #include /* close, dup, unlinkat, write */ /* Některé souborové systémy používají reprezentaci složek, která * není pro velké složky (s velkým počtem souborů) příliš efektivní. * V takové situaci je potřeba velké složky rozdělit na několik * menších. Jedna z nejjednodušších strategií rozčlení soubory podle * prefixu jména, např. takto: * * • abc.txt → a/b/abc.txt * • xyz.txt → x/y/xyz.txt * • xyy.txt → x/y/xyy.txt * * Napište podprogram ‹make_buckets›, který pro vstupní složku * vytvoří takovouto prefixovou hierarchii. Na samotné soubory * vyrobte v cílové složce pouze nové odkazy. Mohou za vám hodit * systémová volání ‹mkdirat› a ‹linkat›. * * Proběhne-li vše v pořádku, podprogram vrátí 0. V opačném * případě vrátí -1. */ int make_buckets( int from_dir_fd, int to_dir_fd ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ #include /* assert */ #include /* err, warn */ #include /* bool, false, true */ static void unlink_if_exists( int fd, const char *name, bool is_dir ) { if ( unlinkat( fd, name, is_dir ? AT_REMOVEDIR : 0 ) == -1 && errno != ENOENT ) err( 2, "unlinking %s", name ); } static void close_or_warn( int fd, const char *name ) { if ( fd >= 0 && close( fd ) == -1 ) warn( "closing %s", name ); } static int open_or_die( int dir_fd, const char *name, int flag ) { int fd = openat( dir_fd, name, flag ); if ( fd == -1 ) err( 2, "opening %s", name ); return fd; } static int mkdir_or_die( int dir_fd, const char *name ) { int fd; if ( mkdirat( dir_fd, name, 0777 ) == -1 && errno != EEXIST ) err( 1, "creating directory %s", name ); if ( ( fd = openat( dir_fd, name, O_DIRECTORY ) ) == -1 ) err( 1, "opening newly created directory %s", name ); return fd; } static int create_file( int dir_fd, const char *name ) { unlink_if_exists( dir_fd, name, false ); int fd = openat( dir_fd, name, O_CREAT | O_TRUNC | O_RDWR, 0666 ); if ( fd == -1 ) err( 2, "creating %s", name ); return fd; } static void write_file( int dir, const char *name, const char *str ) { int fd = create_file( dir, name ); int len = strlen( str ); if ( write( fd, str, len ) != len ) err( 2, "writing file %s", name ); close_or_warn( fd, name ); } static bool check_file( int dir, const char *name ) { int fd = open_or_die( dir, name, O_RDONLY ); struct stat st; if ( fstat( fd, &st ) == -1 ) err( 2, "stat-ing %s", name ); close_or_warn( fd, name ); return st.st_nlink == 2; } static bool find( const char *needle, const char **hay, int size ) { for ( int i = 0; i < size; ++ i ) if ( strcmp( needle, hay[ i ] ) == 0 ) return true; return false; } static bool check_dir( int dir_fd, const char **files, int sfiles, const char **dirs, int sdirs ) { int dup_fd = dup( dir_fd ); if ( dup_fd == -1 ) err( 1, "dup" ); DIR *dir = fdopendir( dup_fd ); if ( !dir ) err( 1, "fdopendir" ); rewinddir( dir ); struct dirent *ptr; struct stat st; int f_total = 0; int d_total = 0; int rv = false; while ( ( ptr = readdir( dir ) ) ) { if ( strcmp( ptr->d_name, "." ) == 0 || strcmp( ptr->d_name, ".." ) == 0 ) continue; if ( fstatat( dir_fd, ptr->d_name, &st, AT_SYMLINK_NOFOLLOW ) == -1 ) err( 1, "fstatat" ); if ( S_ISREG( st.st_mode ) ) { if ( !find( ptr->d_name, files, sfiles ) ) goto out; ++ f_total; } else if ( S_ISDIR( st.st_mode ) ) { if ( !find( ptr->d_name, dirs, sdirs ) ) goto out; ++ d_total; } else goto out; } rv = sfiles == f_total && sdirs == d_total; out: if ( dir ) closedir( dir ); else if ( dup_fd != -1 ) close( dup_fd ); return rv; } static void cleanup( void ) { int from_fd = openat( AT_FDCWD, "zt.r4_from", O_DIRECTORY ); if ( from_fd == -1 ) return; unlink_if_exists( from_fd, "zja.txt", false ); unlink_if_exists( from_fd, "zjb.txt", false ); unlink_if_exists( from_fd, "zjc.txt", false ); close_or_warn( from_fd, "zt.r4_from" ); unlink_if_exists( AT_FDCWD, "zt.r4_from", true ); int to_fd = openat( AT_FDCWD, "zt.r4_to", O_DIRECTORY ); if ( to_fd == -1 ) return; unlink_if_exists( to_fd, "z/j/zja.txt", false ); unlink_if_exists( to_fd, "z/j/zjb.txt", false ); unlink_if_exists( to_fd, "z/j/zjc.txt", false ); unlink_if_exists( to_fd, "z/j", true ); unlink_if_exists( to_fd, "z", true ); close_or_warn( to_fd, "zt.r4_to" ); unlink_if_exists( AT_FDCWD, "zt.r4_to", true ); } int main( void ) { cleanup(); int from_fd = mkdir_or_die( AT_FDCWD, "zt.r4_from" ); int to_fd = mkdir_or_die( AT_FDCWD, "zt.r4_to" ); /* Test case 1. */ assert( ( make_buckets( from_fd, to_fd ) == 0 ) ); assert( ( check_dir( to_fd, NULL, 0, NULL, 0 ) ) ); /* Test case 2. */ write_file( from_fd, "zjb.txt", "Run, Run, Run, Run," ); write_file( from_fd, "zjc.txt", "Run, Run, Run, Run." ); assert( ( make_buckets( from_fd, to_fd ) == 0 ) ); const char *exp1[] = { "zjb.txt", "zjc.txt" }; assert( ( check_dir( from_fd, exp1, 2, NULL, 0 ) ) ); const char *exp2[] = { "z" }; assert( ( check_dir( to_fd, NULL, 0, exp2, 1 ) ) ); const char *exp3[] = { "j" }; int z_dir = open_or_die( to_fd, "z", O_DIRECTORY ); assert( ( check_dir( z_dir, NULL, 0, exp3, 1 ) ) ); const char *exp4[] = { "zjb.txt", "zjc.txt" }; int j_dir = open_or_die( z_dir, "j", O_DIRECTORY ); assert( ( check_dir( j_dir, exp4, 2, NULL, 0 ) ) ); assert( ( check_file( j_dir, "zjb.txt" ) ) ); assert( ( check_file( j_dir, "zjc.txt" ) ) ); /* Test case 3. */ write_file( from_fd, "zja.txt", "RUN LIKE HELL" ); assert( ( make_buckets( from_fd, to_fd ) == 0 ) ); const char *exp5[] = { "zja.txt", "zjb.txt", "zjc.txt" }; assert( ( check_dir( j_dir, exp5, 3, NULL, 0 ) ) ); assert( ( check_file( j_dir, "zja.txt" ) ) ); close_or_warn( j_dir, "zt.r4_to/z/j" ); close_or_warn( z_dir, "zt.r4_to/z" ); close_or_warn( from_fd, "zt.r4_from" ); close_or_warn( to_fd, "zt.r4_to" ); cleanup(); return 0; }