From 9e43e5119d02484e053d0835e81e20e2149af2fc Mon Sep 17 00:00:00 2001 From: muagarwa Date: Wed, 29 Apr 2020 21:15:53 +0530 Subject: [PATCH] cephfs: adding Link, Symlink and Readlink functions Link function implements ceph_link(). Symlink function implements ceph_symlink(). Readlink function implements ceph_readlink(). To fix https://github.com/ceph/go-ceph/issues/218 Signed-off-by: Mudit Agarwal --- cephfs/path.go | 50 ++++++++ cephfs/path_test.go | 297 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+) diff --git a/cephfs/path.go b/cephfs/path.go index a47b86f..865580e 100644 --- a/cephfs/path.go +++ b/cephfs/path.go @@ -56,3 +56,53 @@ func (mount *MountInfo) Unlink(path string) error { ret := C.ceph_unlink(mount.mount, cPath) return getError(ret) } + +// Link creates a new link to an existing file. +// +// Implements: +// int ceph_link (struct ceph_mount_info *cmount, const char *existing, const char *newname); +func (mount *MountInfo) Link(oldname, newname string) error { + cOldname := C.CString(oldname) + defer C.free(unsafe.Pointer(cOldname)) + + cNewname := C.CString(newname) + defer C.free(unsafe.Pointer(cNewname)) + + ret := C.ceph_link(mount.mount, cOldname, cNewname) + return getError(ret) +} + +// Symlink creates a symbolic link to an existing path. +// +// Implements: +// int ceph_symlink(struct ceph_mount_info *cmount, const char *existing, const char *newname); +func (mount *MountInfo) Symlink(existing, newname string) error { + cExisting := C.CString(existing) + defer C.free(unsafe.Pointer(cExisting)) + + cNewname := C.CString(newname) + defer C.free(unsafe.Pointer(cNewname)) + + ret := C.ceph_symlink(mount.mount, cExisting, cNewname) + return getError(ret) +} + +// Readlink returns the value of a symbolic link. +// +// Implements: +// int ceph_readlink(struct ceph_mount_info *cmount, const char *path, char *buf, int64_t size); +func (mount *MountInfo) Readlink(path string) (string, error) { + cPath := C.CString(path) + defer C.free(unsafe.Pointer(cPath)) + + buf := make([]byte, 4096) + ret := C.ceph_readlink(mount.mount, + cPath, + (*C.char)(unsafe.Pointer(&buf[0])), + C.int64_t(len(buf))) + if ret < 0 { + return "", getError(ret) + } + + return string(buf[:ret]), nil +} diff --git a/cephfs/path_test.go b/cephfs/path_test.go index 2d593d4..bb8bd92 100644 --- a/cephfs/path_test.go +++ b/cephfs/path_test.go @@ -60,3 +60,300 @@ func TestRemoveDir(t *testing.T) { assert.EqualError(t, err, fmt.Sprintf("stat %s: no such file or directory", localPath)) } + +func TestLink(t *testing.T) { + mount := fsConnect(t) + defer fsDisconnect(t, mount) + + t.Run("rootDirOperations", func(t *testing.T) { + // Root dir, both as source and destination. + err := mount.Link("/", "/") + // Error directory operations are not allowed. + assert.Error(t, err) + + dir1 := "myDir1" + assert.NoError(t, mount.MakeDir(dir1, 0755)) + defer func() { + assert.NoError(t, mount.RemoveDir(dir1)) + }() + + // Creating link for a directory. + err = mount.Link(dir1, "/") + // Error, directory operations not allowed. + assert.Error(t, err) + }) + + // Non-root directory operations. + fname := "testFile.txt" + dir2 := "myDir2" + assert.NoError(t, mount.MakeDir(dir2, 0755)) + defer func() { + assert.NoError(t, mount.RemoveDir(dir2)) + }() + + t.Run("dirAsSource", func(t *testing.T) { + err := mount.Link(dir2, fname) + // Error, directory operations not allowed. + assert.Error(t, err) + }) + + t.Run("dirAsDestination", func(t *testing.T) { + f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE, 0666) + assert.NotNil(t, f1) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Close()) + assert.NoError(t, mount.Unlink(fname)) + }() + err = mount.Link(fname, dir2) + // Error, destination exists. + assert.Error(t, err) + }) + + // File operations. + t.Run("sourceDoesNotExist", func(t *testing.T) { + fname := "notExist.txt" + err := mount.Link(fname, "hardlnk") + // Error, file does not exist. + assert.Error(t, err) + }) + + t.Run("sourceExistsSuccess", func(t *testing.T) { + useMount(t) + + fname1 := "TestFile1.txt" + f1, err := mount.Open(fname1, os.O_WRONLY|os.O_CREATE, 0666) + assert.NotNil(t, f1) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f1.Close()) + assert.NoError(t, mount.Unlink(fname1)) + }() + err = mount.Link(fname1, "hardlnk") + defer func() { assert.NoError(t, mount.Unlink("hardlnk")) }() + // No error, normal link operation. + assert.NoError(t, err) + // Verify that link got created. + _, err = os.Stat(path.Join(CephMountDir, "hardlnk")) + assert.NoError(t, err) + }) + + t.Run("destExistsError", func(t *testing.T) { + // Create hard link when destination exists. + fname2 := "TestFile2.txt" + fname3 := "TestFile3.txt" + f2, err := mount.Open(fname2, os.O_WRONLY|os.O_CREATE, 0666) + assert.NotNil(t, f2) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f2.Close()) + assert.NoError(t, mount.Unlink(fname2)) + }() + f3, err := mount.Open(fname3, os.O_WRONLY|os.O_CREATE, 0666) + assert.NotNil(t, f3) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f3.Close()) + assert.NoError(t, mount.Unlink(fname3)) + }() + err = mount.Link(fname2, fname3) + // Error, destination already exists. + assert.Error(t, err) + }) +} + +func TestUnlink(t *testing.T) { + mount := fsConnect(t) + defer fsDisconnect(t, mount) + + t.Run("fileUnlink", func(t *testing.T) { + fname := "TestFile.txt" + err := mount.Unlink(fname) + // Error, file does not exist. + assert.Error(t, err) + + f, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE, 0666) + assert.NotNil(t, f) + assert.NoError(t, err) + defer func() { + assert.NoError(t, f.Close()) + assert.NoError(t, mount.Unlink(fname)) + }() + assert.NoError(t, mount.Link(fname, "hardlnk")) + + err = mount.Unlink("hardlnk") + // No Error, link will be removed. + assert.NoError(t, err) + }) + + t.Run("dirUnlink", func(t *testing.T) { + dirname := "/a" + err := mount.MakeDir(dirname, 0755) + assert.NoError(t, err) + defer func() { + assert.NoError(t, mount.RemoveDir(dirname)) + }() + + err = mount.Unlink(dirname) + // Error, not permitted on directory. + assert.Error(t, err) + }) +} + +func TestSymlink(t *testing.T) { + mount := fsConnect(t) + defer fsDisconnect(t, mount) + + // File operations. + t.Run("sourceDoesNotExistSuccess", func(t *testing.T) { + useMount(t) + fname1 := "TestFile1.txt" + err := mount.Symlink(fname1, "Symlnk1") + // No Error, symlink works even if source file doesn't exist. + assert.NoError(t, err) + _, err = os.Stat(path.Join(CephMountDir, "Symlnk1")) + // Error, source is not there. + assert.Error(t, err) + + localPath := path.Join(CephMountDir, fname1) + _, err = os.Stat(localPath) + // Error, source file is still not there. + assert.Error(t, err) + }) + + t.Run("symlinkExistsError", func(t *testing.T) { + fname1 := "TestFile1.txt" + f1, err := mount.Open(fname1, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, f1) + defer func() { + assert.NoError(t, f1.Close()) + assert.NoError(t, mount.Unlink(fname1)) + }() + err = mount.Symlink(fname1, "Symlnk1") + // Error, Symlink1 exists. + assert.Error(t, err) + defer func() { + assert.NoError(t, mount.Unlink("Symlnk1")) + }() + }) + + t.Run("sourceExistsSuccess", func(t *testing.T) { + useMount(t) + fname2 := "TestFile2.txt" + f2, err := mount.Open(fname2, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, f2) + defer func() { + assert.NoError(t, f2.Close()) + assert.NoError(t, mount.Unlink(fname2)) + }() + err = mount.Symlink(fname2, "Symlnk2") + assert.NoError(t, err) + defer func() { + assert.NoError(t, mount.Unlink("Symlnk2")) + }() + _, err = os.Stat(path.Join(CephMountDir, "Symlnk2")) + assert.NoError(t, err) + }) + + // Directory operations. + t.Run("rootDirOps", func(t *testing.T) { + err := mount.Symlink("/", "/") + assert.Error(t, err) + + err = mount.Symlink("/", "someDir") + assert.NoError(t, err) + + err = mount.Symlink("someFile", "/") + // Error, permission denied. + assert.Error(t, err) + }) + + t.Run("nonRootDir", func(t *testing.T) { + useMount(t) + // 1. Create a directory. + // 2. Create a symlink to that directory. + // 3. Create a file inside symlink. + // 4. Ensure that it is not a directory. + dirname := "mydir" + err := mount.MakeDir(dirname, 0755) + assert.NoError(t, err) + defer func() { + assert.NoError(t, mount.RemoveDir(dirname)) + }() + + err = mount.Symlink(dirname, "symlnk") + assert.NoError(t, err) + defer func() { + assert.NoError(t, mount.Unlink("symlnk")) + }() + + fname := "symlnk/file" + f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, f1) + defer func() { + assert.NoError(t, f1.Close()) + assert.NoError(t, mount.Unlink(fname)) + }() + var fileInfo os.FileInfo + fileInfo, err = os.Stat(path.Join(CephMountDir, "symlnk/file")) + assert.NoError(t, err) + assert.Equal(t, fileInfo.IsDir(), false) + }) +} + +func TestReadlink(t *testing.T) { + mount := fsConnect(t) + defer fsDisconnect(t, mount) + + t.Run("regularFile", func(t *testing.T) { + fname := "file1.txt" + f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, f1) + defer func() { + assert.NoError(t, f1.Close()) + assert.NoError(t, mount.Unlink(fname)) + }() + + buf, err := mount.Readlink(fname) + // Error, given path is not symbolic link. + assert.Error(t, err) + assert.Equal(t, buf, "") + }) + + t.Run("symLink", func(t *testing.T) { + path1 := "path1" + path2 := "path2" + assert.NoError(t, mount.Symlink(path1, path2)) + defer func() { + assert.NoError(t, mount.Unlink(path2)) + }() + buf, err := mount.Readlink(path2) + assert.NoError(t, err) + assert.Equal(t, buf, path1) + }) + + t.Run("hardLink", func(t *testing.T) { + path3 := "path3" + path4 := "path4" + p, err := mount.Open(path3, os.O_RDWR|os.O_CREATE, 0666) + assert.NoError(t, err) + assert.NotNil(t, p) + defer func() { + assert.NoError(t, p.Close()) + assert.NoError(t, mount.Unlink(path3)) + }() + + assert.NoError(t, mount.Link(path3, path4)) + defer func() { + assert.NoError(t, mount.Unlink(path4)) + }() + buf, err := mount.Readlink(path4) + // Error, path4 is not symbolic link. + assert.Error(t, err) + assert.Equal(t, buf, "") + }) +}