| /* |
| * Copyright (c) International Business Machines Corp., 2006 |
| * Author: Yi Yang <yyangcdl@cn.ibm.com> |
| * |
| * 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, write to the Free Software Foundation, |
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| /* |
| * Description: |
| * Verify that, |
| * 1) renameat(2) returns -1 and sets errno to EBADF if olddirfd |
| * or newdirfd is not a valid file descriptor. |
| * 2) renameat(2) returns -1 and sets errno to ENOTDIR if oldpath |
| * is relative and olddirfd is a file descriptor referring to |
| * a file other than a directory, or similar for newpath and |
| * newdirfd. |
| * 3) renameat(2) returns -1 and sets errno to ELOOP if too many |
| * symbolic links were encountered in resolving oldpath or |
| * newpath. |
| * 4) renameat(2) returns -1 and sets errno to EROFS if the file |
| * is on a read-only file system. |
| * 5) renameat(2) returns -1 and sets errno to EMLINK if oldpath |
| * already has the maximum number of links to it, or it is a |
| * directory and the directory containing newpath has the |
| * maximum number of links. |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <fcntl.h> |
| #include <error.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <sys/mount.h> |
| |
| #include "test.h" |
| #include "usctest.h" |
| #include "safe_macros.h" |
| #include "lapi/fcntl.h" |
| #include "lapi/renameat.h" |
| |
| #define MNTPOINT "mntpoint" |
| #define TESTDIR "testdir" |
| #define NEW_TESTDIR "new_testdir" |
| #define TESTDIR2 "/loopdir" |
| #define NEW_TESTDIR2 "newloopdir" |
| #define TESTDIR3 "emlinkdir" |
| #define NEW_TESTDIR3 "testemlinkdir/new_emlinkdir" |
| #define TESTFILE "testfile" |
| #define NEW_TESTFILE "new_testfile" |
| #define TESTFILE2 "testfile2" |
| #define NEW_TESTFILE2 "new_testfile2" |
| #define TESTFILE3 "testdir/testfile" |
| #define TESTFILE4 "testfile4" |
| #define TESTFILE5 "mntpoint/rofile" |
| #define NEW_TESTFILE5 "mntpoint/newrofile" |
| |
| #define DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO) |
| #define FILEMODE (S_IRWXU | S_IRWXG | S_IRWXO) |
| |
| static int curfd = AT_FDCWD; |
| static int olddirfd; |
| static int newdirfd; |
| static int badfd = 100; |
| static int filefd; |
| static char absoldpath[256]; |
| static char absnewpath[256]; |
| static char looppathname[sizeof(TESTDIR2) * 43] = "."; |
| static int max_subdirs; |
| |
| static int mount_flag; |
| static const char *device; |
| |
| static struct test_case_t { |
| int *oldfdptr; |
| const char *oldpath; |
| int *newfdptr; |
| const char *newpath; |
| int exp_errno; |
| } test_cases[] = { |
| { &curfd, TESTFILE, &curfd, NEW_TESTFILE, 0 }, |
| { &olddirfd, TESTFILE, &newdirfd, NEW_TESTFILE, 0 }, |
| { &olddirfd, absoldpath, &newdirfd, absnewpath, 0 }, |
| { &badfd, TESTFILE, &badfd, NEW_TESTFILE, EBADF }, |
| { &filefd, TESTFILE, &filefd, NEW_TESTFILE, ENOTDIR }, |
| { &curfd, looppathname, &curfd, NEW_TESTDIR2, ELOOP }, |
| { &curfd, TESTFILE5, &curfd, NEW_TESTFILE5, EROFS }, |
| { &curfd, TESTDIR3, &curfd, NEW_TESTDIR3, EMLINK }, |
| }; |
| |
| static void setup(void); |
| static void cleanup(void); |
| static void renameat_verify(const struct test_case_t *); |
| |
| char *TCID = "renameat01"; |
| int TST_TOTAL = ARRAY_SIZE(test_cases); |
| |
| int main(int ac, char **av) |
| { |
| int i, lc; |
| const char *msg; |
| |
| msg = parse_opts(ac, av, NULL, NULL); |
| if (msg != NULL) |
| tst_brkm(TBROK, NULL, "OPTION PARSING ERROR - %s", msg); |
| |
| setup(); |
| |
| for (lc = 0; TEST_LOOPING(lc); lc++) { |
| tst_count = 0; |
| |
| for (i = 0; i < TST_TOTAL; i++) |
| renameat_verify(&test_cases[i]); |
| } |
| |
| cleanup(); |
| tst_exit(); |
| } |
| |
| static void setup(void) |
| { |
| char *tmpdir; |
| const char *fs_type; |
| int i; |
| |
| if ((tst_kvercmp(2, 6, 16)) < 0) { |
| tst_brkm(TCONF, NULL, |
| "This test can only run on kernels that are " |
| "2.6.16 and higher"); |
| } |
| |
| tst_require_root(NULL); |
| |
| tst_sig(NOFORK, DEF_HANDLER, cleanup); |
| |
| tst_tmpdir(); |
| |
| fs_type = tst_dev_fs_type(); |
| device = tst_acquire_device(cleanup); |
| |
| if (!device) |
| tst_brkm(TCONF, cleanup, "Failed to obtain block device"); |
| |
| TEST_PAUSE; |
| |
| SAFE_TOUCH(cleanup, TESTFILE, FILEMODE, NULL); |
| |
| SAFE_TOUCH(cleanup, TESTFILE2, FILEMODE, NULL); |
| tmpdir = tst_get_tmpdir(); |
| sprintf(absoldpath, "%s/%s", tmpdir, TESTFILE2); |
| sprintf(absnewpath, "%s/%s", tmpdir, NEW_TESTFILE2); |
| free(tmpdir); |
| |
| SAFE_MKDIR(cleanup, TESTDIR, DIRMODE); |
| SAFE_TOUCH(cleanup, TESTFILE3, FILEMODE, NULL); |
| SAFE_MKDIR(cleanup, NEW_TESTDIR, DIRMODE); |
| |
| olddirfd = SAFE_OPEN(cleanup, TESTDIR, O_DIRECTORY); |
| newdirfd = SAFE_OPEN(cleanup, NEW_TESTDIR, O_DIRECTORY); |
| |
| filefd = SAFE_OPEN(cleanup, TESTFILE4, |
| O_RDWR | O_CREAT, FILEMODE); |
| |
| /* |
| * NOTE: the ELOOP test is written based on that the |
| * consecutive symlinks limit in kernel is hardwired |
| * to 40. |
| */ |
| SAFE_MKDIR(cleanup, "loopdir", DIRMODE); |
| SAFE_SYMLINK(cleanup, "../loopdir", "loopdir/loopdir"); |
| for (i = 0; i < 43; i++) |
| strcat(looppathname, TESTDIR2); |
| |
| tst_mkfs(cleanup, device, fs_type, NULL); |
| SAFE_MKDIR(cleanup, MNTPOINT, DIRMODE); |
| if (mount(device, MNTPOINT, fs_type, 0, NULL) < 0) { |
| tst_brkm(TBROK | TERRNO, cleanup, |
| "mount device:%s failed", device); |
| } |
| mount_flag = 1; |
| SAFE_TOUCH(cleanup, TESTFILE5, FILEMODE, NULL); |
| if (mount(device, MNTPOINT, fs_type, |
| MS_REMOUNT | MS_RDONLY, NULL) < 0) { |
| tst_brkm(TBROK | TERRNO, cleanup, |
| "mount device:%s failed", device); |
| } |
| |
| SAFE_MKDIR(cleanup, TESTDIR3, DIRMODE); |
| max_subdirs = tst_fs_fill_subdirs(cleanup, "testemlinkdir"); |
| } |
| |
| static void renameat_verify(const struct test_case_t *tc) |
| { |
| if (tc->exp_errno == EMLINK && max_subdirs == 0) { |
| tst_resm(TCONF, "EMLINK test is not appropriate"); |
| return; |
| } |
| |
| TEST(renameat(*(tc->oldfdptr), tc->oldpath, |
| *(tc->newfdptr), tc->newpath)); |
| |
| if (tc->exp_errno && TEST_RETURN != -1) { |
| tst_resm(TFAIL, "renameat() succeeded unexpectedly"); |
| return; |
| } |
| |
| if (tc->exp_errno == 0 && TEST_RETURN != 0) { |
| tst_resm(TFAIL | TTERRNO, "renameat() failed unexpectedly"); |
| return; |
| } |
| |
| if (TEST_ERRNO == tc->exp_errno) { |
| tst_resm(TPASS | TTERRNO, |
| "renameat() returned the expected value"); |
| } else { |
| tst_resm(TFAIL | TTERRNO, |
| "renameat() got unexpected return value; expected: " |
| "%d - %s", tc->exp_errno, |
| strerror(tc->exp_errno)); |
| } |
| |
| if (TEST_ERRNO == 0 && renameat(*(tc->newfdptr), tc->newpath, |
| *(tc->oldfdptr), tc->oldpath) < 0) { |
| tst_brkm(TBROK | TERRNO, cleanup, "renameat(%d, %s, " |
| "%d, %s) failed.", *(tc->newfdptr), tc->newpath, |
| *(tc->oldfdptr), tc->oldpath); |
| } |
| } |
| |
| static void cleanup(void) |
| { |
| if (olddirfd > 0 && close(olddirfd) < 0) |
| tst_resm(TWARN | TERRNO, "close olddirfd failed"); |
| |
| if (newdirfd > 0 && close(newdirfd) < 0) |
| tst_resm(TWARN | TERRNO, "close newdirfd failed"); |
| |
| if (filefd > 0 && close(filefd) < 0) |
| tst_resm(TWARN | TERRNO, "close filefd failed"); |
| |
| if (mount_flag && tst_umount(MNTPOINT) < 0) |
| tst_resm(TWARN | TERRNO, "umount %s failed", MNTPOINT); |
| |
| if (device) |
| tst_release_device(NULL, device); |
| |
| tst_rmdir(); |
| } |