summaryrefslogtreecommitdiffstats
path: root/src/kernel/tests/lib/tst_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kernel/tests/lib/tst_device.c')
-rw-r--r--src/kernel/tests/lib/tst_device.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/src/kernel/tests/lib/tst_device.c b/src/kernel/tests/lib/tst_device.c
new file mode 100644
index 0000000..0df8efe
--- /dev/null
+++ b/src/kernel/tests/lib/tst_device.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2014 Cyril Hrubis chrubis@suse.cz
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it would be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Further, this software is distributed without any warranty that it is
+ * free of the rightful claim of any third person regarding infringement
+ * or the like. Any license provided herein, whether implied or
+ * otherwise, applies only to this software file. Patent licenses, if
+ * any, provided herein do not apply to combinations of this program with
+ * other software, or any other product whatsoever.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <uapi_xloop.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/sysmacros.h>
+#include "lapi/syscalls.h"
+#include "test.h"
+#include "safe_macros.h"
+
+#ifndef XLOOP_CTL_GET_FREE
+# define XLOOP_CTL_GET_FREE 0x4C82
+#endif
+
+#define XLOOP_CONTROL_FILE "/dev/xloop-control"
+
+#define DEV_FILE "test_dev.img"
+#define DEV_SIZE_MB 256u
+
+static char dev_path[1024];
+static int device_acquired;
+static unsigned long prev_dev_sec_write;
+
+static const char *dev_variants[] = {
+ "/dev/xloop%i",
+ "/dev/xloop/%i",
+ "/dev/block/xloop%i"
+};
+
+static int set_dev_path(int dev, char *path, size_t path_len)
+{
+ unsigned int i;
+ struct stat st;
+
+ for (i = 0; i < ARRAY_SIZE(dev_variants); i++) {
+ snprintf(path, path_len, dev_variants[i], dev);
+
+ if (stat(path, &st) == 0 && S_ISBLK(st.st_mode))
+ return 1;
+ }
+
+ return 0;
+}
+
+int tst_find_free_xloopdev(char *path, size_t path_len)
+{
+ int ctl_fd, dev_fd, rc, i;
+ struct xloop_info xloopinfo;
+ char buf[1024];
+
+ /* since Linux 3.1 */
+ ctl_fd = open(XLOOP_CONTROL_FILE, O_RDWR);
+
+ if (ctl_fd > 0) {
+ rc = ioctl(ctl_fd, XLOOP_CTL_GET_FREE);
+ close(ctl_fd);
+ if (rc >= 0) {
+ if (path)
+ set_dev_path(rc, path, path_len);
+ tst_resm(TINFO, "Found free device %d '%s'",
+ rc, path ?: "");
+ return rc;
+ }
+ tst_resm(TINFO, "Couldn't find free xloop device");
+ return -1;
+ }
+
+ switch (errno) {
+ case ENOENT:
+ break;
+ case EACCES:
+ tst_resm(TINFO | TERRNO,
+ "Not allowed to open " XLOOP_CONTROL_FILE ". "
+ "Are you root?");
+ break;
+ default:
+ tst_resm(TBROK | TERRNO, "Failed to open " XLOOP_CONTROL_FILE);
+ }
+
+ /*
+ * Older way is to iterate over /dev/xloop%i and /dev/xloop/%i and try
+ * XLOOP_GET_STATUS ioctl() which fails for free xloop devices.
+ */
+ for (i = 0; i < 256; i++) {
+
+ if (!set_dev_path(i, buf, sizeof(buf)))
+ continue;
+
+ dev_fd = open(buf, O_RDONLY);
+
+ if (dev_fd < 0)
+ continue;
+
+ if (ioctl(dev_fd, XLOOP_GET_STATUS, &xloopinfo) == 0) {
+ tst_resm(TINFO, "Device '%s' in use", buf);
+ } else {
+ if (errno != ENXIO)
+ continue;
+ tst_resm(TINFO, "Found free device '%s'", buf);
+ close(dev_fd);
+ if (path != NULL) {
+ strncpy(path, buf, path_len);
+ path[path_len-1] = '\0';
+ }
+ return i;
+ }
+
+ close(dev_fd);
+ }
+
+ tst_resm(TINFO, "No free devices found");
+
+ return -1;
+}
+
+int tst_attach_device(const char *dev, const char *file)
+{
+ int dev_fd, file_fd;
+ struct xloop_info xloopinfo;
+
+ dev_fd = open(dev, O_RDWR);
+ if (dev_fd < 0) {
+ tst_resm(TWARN | TERRNO, "open('%s', O_RDWR) failed", dev);
+ return 1;
+ }
+
+ file_fd = open(file, O_RDWR);
+ if (file_fd < 0) {
+ tst_resm(TWARN | TERRNO, "open('%s', O_RDWR) failed", file);
+ close(dev_fd);
+ return 1;
+ }
+
+ if (ioctl(dev_fd, XLOOP_SET_FD, file_fd) < 0) {
+ close(dev_fd);
+ close(file_fd);
+ tst_resm(TWARN | TERRNO, "ioctl(%s, XLOOP_SET_FD, %s) failed",
+ dev, file);
+ return 1;
+ }
+
+ /* Old mkfs.btrfs use XLOOP_GET_STATUS instead of backing_file to get
+ * associated filename, so we need to set up the device by calling
+ * XLOOP_SET_FD and XLOOP_SET_STATUS.
+ */
+ memset(&xloopinfo, 0, sizeof(xloopinfo));
+ strcpy(xloopinfo.xlo_name, file);
+
+ if (ioctl(dev_fd, XLOOP_SET_STATUS, &xloopinfo)) {
+ close(dev_fd);
+ close(file_fd);
+ tst_resm(TWARN | TERRNO,
+ "ioctl(%s, XLOOP_SET_STATUS, %s) failed", dev, file);
+ return 1;
+ }
+
+ close(dev_fd);
+ close(file_fd);
+ return 0;
+}
+
+int tst_detach_device_by_fd(const char *dev, int dev_fd)
+{
+ int ret, i;
+
+ /* keep trying to clear XLOOPDEV until we get ENXIO, a quick succession
+ * of attach/detach might not give udev enough time to complete */
+ for (i = 0; i < 40; i++) {
+ ret = ioctl(dev_fd, XLOOP_CLR_FD, 0);
+
+ if (ret && (errno == ENXIO))
+ return 0;
+
+ if (ret && (errno != EBUSY)) {
+ tst_resm(TWARN,
+ "ioctl(%s, XLOOP_CLR_FD, 0) unexpectedly failed with: %s",
+ dev, tst_strerrno(errno));
+ return 1;
+ }
+
+ usleep(50000);
+ }
+
+ tst_resm(TWARN,
+ "ioctl(%s, XLOOP_CLR_FD, 0) no ENXIO for too long", dev);
+ return 1;
+}
+
+int tst_detach_device(const char *dev)
+{
+ int dev_fd, ret;
+
+ dev_fd = open(dev, O_RDONLY);
+ if (dev_fd < 0) {
+ tst_resm(TWARN | TERRNO, "open(%s) failed", dev);
+ return 1;
+ }
+
+ ret = tst_detach_device_by_fd(dev, dev_fd);
+ close(dev_fd);
+ return ret;
+}
+
+int tst_dev_sync(int fd)
+{
+ return syscall(__NR_syncfs, fd);
+}
+
+const char *tst_acquire_xloop_device(unsigned int size, const char *filename)
+{
+ unsigned int acq_dev_size = MAX(size, DEV_SIZE_MB);
+
+ if (tst_prealloc_file(filename, 1024 * 1024, acq_dev_size)) {
+ tst_resm(TWARN | TERRNO, "Failed to create %s", filename);
+ return NULL;
+ }
+
+ if (tst_find_free_xloopdev(dev_path, sizeof(dev_path)) == -1)
+ return NULL;
+
+ if (tst_attach_device(dev_path, filename))
+ return NULL;
+
+ return dev_path;
+}
+
+const char *tst_acquire_device__(unsigned int size)
+{
+ int fd;
+ const char *dev;
+ struct stat st;
+ unsigned int acq_dev_size;
+ uint64_t ltp_dev_size;
+
+ acq_dev_size = MAX(size, DEV_SIZE_MB);
+
+ dev = getenv("LTP_DEV");
+
+ if (dev) {
+ tst_resm(TINFO, "Using test device LTP_DEV='%s'", dev);
+
+ if (stat(dev, &st)) {
+ tst_resm(TWARN | TERRNO, "stat() failed");
+ return NULL;
+ }
+
+ if (!S_ISBLK(st.st_mode)) {
+ tst_resm(TWARN, "%s is not a block device", dev);
+ return NULL;
+ }
+
+ fd = open(dev, O_RDONLY);
+ if (fd < 0) {
+ tst_resm(TWARN | TERRNO,
+ "open(%s, O_RDONLY) failed", dev);
+ return NULL;
+ }
+
+ if (ioctl(fd, BLKGETSIZE64, &ltp_dev_size)) {
+ tst_resm(TWARN | TERRNO,
+ "ioctl(fd, BLKGETSIZE64, ...) failed");
+ close(fd);
+ return NULL;
+ }
+
+ if (close(fd)) {
+ tst_resm(TWARN | TERRNO,
+ "close(fd) failed");
+ return NULL;
+ }
+
+ ltp_dev_size = ltp_dev_size/1024/1024;
+
+ if (acq_dev_size <= ltp_dev_size)
+ return dev;
+
+ tst_resm(TINFO, "Skipping $LTP_DEV size %"PRIu64"MB, requested size %uMB",
+ ltp_dev_size, acq_dev_size);
+ }
+
+ dev = tst_acquire_xloop_device(acq_dev_size, DEV_FILE);
+
+ if (dev)
+ device_acquired = 1;
+
+ return dev;
+}
+
+const char *tst_acquire_device_(void (cleanup_fn)(void), unsigned int size)
+{
+ const char *device;
+
+ if (device_acquired) {
+ tst_brkm(TBROK, cleanup_fn, "Device already acquired");
+ return NULL;
+ }
+
+ if (!tst_tmpdir_created()) {
+ tst_brkm(TBROK, cleanup_fn,
+ "Cannot acquire device without tmpdir() created");
+ return NULL;
+ }
+
+ device = tst_acquire_device__(size);
+
+ if (!device) {
+ tst_brkm(TBROK, cleanup_fn, "Failed to acquire device");
+ return NULL;
+ }
+
+ return device;
+}
+
+int tst_release_device(const char *dev)
+{
+ int ret;
+
+ if (!device_acquired)
+ return 0;
+
+ /*
+ * Loop device was created -> we need to detach it.
+ *
+ * The file image is deleted in tst_rmdir();
+ */
+ ret = tst_detach_device(dev);
+
+ device_acquired = 0;
+
+ return ret;
+}
+
+int tst_clear_device(const char *dev)
+{
+ if (tst_fill_file(dev, 0, 1024, 512)) {
+ tst_resm(TWARN, "Failed to clear 512k block on %s", dev);
+ return 1;
+ }
+
+ return 0;
+}
+
+int tst_umount(const char *path)
+{
+ int err, ret, i;
+
+ for (i = 0; i < 50; i++) {
+ ret = umount(path);
+ err = errno;
+
+ if (!ret)
+ return 0;
+
+ if (err != EBUSY) {
+ tst_resm(TWARN, "umount('%s') failed with %s",
+ path, tst_strerrno(err));
+ errno = err;
+ return ret;
+ }
+
+ tst_resm(TINFO, "umount('%s') failed with %s, try %2i...",
+ path, tst_strerrno(err), i+1);
+
+ if (i == 0) {
+ tst_resm(TINFO, "Likely gvfsd-trash is probing newly "
+ "mounted fs, kill it to speed up tests.");
+ }
+
+ usleep(100000);
+ }
+
+ tst_resm(TWARN, "Failed to umount('%s') after 50 retries", path);
+ errno = err;
+ return -1;
+}
+
+int tst_is_mounted(const char *path)
+{
+ char line[PATH_MAX];
+ FILE *file;
+ int ret = 0;
+
+ file = SAFE_FOPEN(NULL, "/proc/mounts", "r");
+
+ while (fgets(line, sizeof(line), file)) {
+ if (strstr(line, path) != NULL) {
+ ret = 1;
+ break;
+ }
+ }
+
+ SAFE_FCLOSE(NULL, file);
+
+ if (!ret)
+ tst_resm(TINFO, "No device is mounted at %s", path);
+
+ return ret;
+}
+
+int tst_is_mounted_at_tmpdir(const char *path)
+{
+ char cdir[PATH_MAX], mpath[PATH_MAX];
+ int ret;
+
+ if (!getcwd(cdir, PATH_MAX)) {
+ tst_resm(TWARN | TERRNO, "Failed to find current directory");
+ return 0;
+ }
+
+ ret = snprintf(mpath, PATH_MAX, "%s/%s", cdir, path);
+ if (ret < 0 || ret >= PATH_MAX) {
+ tst_resm(TWARN | TERRNO,
+ "snprintf() should have returned %d instead of %d",
+ PATH_MAX, ret);
+ return 0;
+ }
+
+ return tst_is_mounted(mpath);
+}
+
+int find_stat_file(const char *dev, char *path, size_t path_len)
+{
+ const char *devname = strrchr(dev, '/') + 1;
+
+ snprintf(path, path_len, "/sys/block/%s/stat", devname);
+
+ if (!access(path, F_OK))
+ return 1;
+
+ DIR *dir = SAFE_OPENDIR(NULL, "/sys/block/");
+ struct dirent *ent;
+
+ while ((ent = readdir(dir))) {
+ snprintf(path, path_len, "/sys/block/%s/%s/stat", ent->d_name, devname);
+
+ if (!access(path, F_OK)) {
+ SAFE_CLOSEDIR(NULL, dir);
+ return 1;
+ }
+ }
+
+ SAFE_CLOSEDIR(NULL, dir);
+ return 0;
+}
+
+unsigned long tst_dev_bytes_written(const char *dev)
+{
+ unsigned long dev_sec_write = 0, dev_bytes_written, io_ticks = 0;
+ char dev_stat_path[1024];
+
+ if (!find_stat_file(dev, dev_stat_path, sizeof(dev_stat_path)))
+ tst_brkm(TCONF, NULL, "Test device stat file: %s not found",
+ dev_stat_path);
+
+ SAFE_FILE_SCANF(NULL, dev_stat_path,
+ "%*s %*s %*s %*s %*s %*s %lu %*s %*s %lu",
+ &dev_sec_write, &io_ticks);
+
+ if (!io_ticks)
+ tst_brkm(TCONF, NULL, "Test device stat file: %s broken",
+ dev_stat_path);
+
+ dev_bytes_written = (dev_sec_write - prev_dev_sec_write) * 512;
+
+ prev_dev_sec_write = dev_sec_write;
+
+ return dev_bytes_written;
+}
+
+void tst_find_backing_dev(const char *path, char *dev)
+{
+ char fmt[20];
+ struct stat buf;
+ FILE *file;
+ char line[PATH_MAX];
+ char *pre = NULL;
+ char *next = NULL;
+
+ if (stat(path, &buf) < 0)
+ tst_brkm(TWARN | TERRNO, NULL, "stat() failed");
+
+ snprintf(fmt, sizeof(fmt), "%u:%u", major(buf.st_dev), minor(buf.st_dev));
+ file = SAFE_FOPEN(NULL, "/proc/self/mountinfo", "r");
+
+ while (fgets(line, sizeof(line), file)) {
+ if (strstr(line, fmt) != NULL) {
+ pre = strstr(line, " - ");
+ pre = strtok_r(pre, " ", &next);
+ pre = strtok_r(NULL, " ", &next);
+ pre = strtok_r(NULL, " ", &next);
+ strcpy(dev, pre);
+ break;
+ }
+ }
+
+ SAFE_FCLOSE(NULL, file);
+
+ if (stat(dev, &buf) < 0)
+ tst_brkm(TWARN | TERRNO, NULL, "stat(%s) failed", dev);
+
+ if (S_ISBLK(buf.st_mode) != 1)
+ tst_brkm(TCONF, NULL, "dev(%s) isn't a block dev", dev);
+}