aboutsummaryrefslogtreecommitdiffstats
path: root/libmount/src
diff options
context:
space:
mode:
authorKarel Zak <kzak@redhat.com>2022-05-25 14:01:52 +0200
committerKarel Zak <kzak@redhat.com>2023-01-03 12:53:12 +0100
commitb0a165dc76c024614a930325877a25555d4ff819 (patch)
tree3aeb54c393c16da209ee34f012e63ef6f45403d9 /libmount/src
parent17de67aa1d7dee8cb8ea3040867a989af44a8243 (diff)
downloadutil-linux-b0a165dc76c024614a930325877a25555d4ff819.tar.gz
libmount: reimplement X-mount.subdir= by hooks
* "prepare-target hook" checks for X-mount.subdir= and define pre-mount hook * "mount-pre hook" unshares temporary directory, redirect the next mount(2) to this temporary directory * "mount-post hook" binds subdirectory to the final target, umounts temporary directory Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'libmount/src')
-rw-r--r--libmount/src/Makemodule.am1
-rw-r--r--libmount/src/context.c4
-rw-r--r--libmount/src/context_mount.c106
-rw-r--r--libmount/src/hook_subdir.c316
-rw-r--r--libmount/src/hooks.c18
-rw-r--r--libmount/src/mountP.h3
-rw-r--r--libmount/src/utils.c73
7 files changed, 333 insertions, 188 deletions
diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am
index 1e7ec24d0d..90cc4ac266 100644
--- a/libmount/src/Makemodule.am
+++ b/libmount/src/Makemodule.am
@@ -34,6 +34,7 @@ libmount_la_SOURCES += \
libmount/src/context_umount.c \
libmount/src/hook_mount_legacy.c \
libmount/src/hook_mkdir.c \
+ libmount/src/hook_subdir.c \
libmount/src/monitor.c
if HAVE_BTRFS
diff --git a/libmount/src/context.c b/libmount/src/context.c
index 840549d86e..2c909b19e0 100644
--- a/libmount/src/context.c
+++ b/libmount/src/context.c
@@ -158,7 +158,6 @@ int mnt_reset_context(struct libmnt_context *cxt)
free(cxt->helper);
free(cxt->orig_user);
- free(cxt->subdir);
cxt->tgt_owner = (uid_t) -1;
cxt->tgt_group = (gid_t) -1;
@@ -173,7 +172,6 @@ int mnt_reset_context(struct libmnt_context *cxt)
cxt->user_mountflags = 0;
cxt->orig_mountflags = 0;
cxt->mountdata = NULL;
- cxt->subdir = NULL;
cxt->flags = MNT_FL_DEFAULT;
cxt->noautofs = 1;
cxt->is_propagation_only = 0;
@@ -299,8 +297,6 @@ struct libmnt_context *mnt_copy_context(struct libmnt_context *o)
goto failed;
if (strdup_between_structs(n, o, orig_user))
goto failed;
- if (strdup_between_structs(n, o, subdir))
- goto failed;
n->mountflags = o->mountflags;
n->mountdata = o->mountdata;
diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c
index 990cc11f82..e176607551 100644
--- a/libmount/src/context_mount.c
+++ b/libmount/src/context_mount.c
@@ -572,29 +572,6 @@ static int exec_helper(struct libmnt_context *cxt)
return rc;
}
-static int do_mount_subdir(struct libmnt_context *cxt,
- const char *root,
- const char *subdir,
- const char *target)
-{
- char *src = NULL;
- int rc = 0;
-
- if (asprintf(&src, "%s/%s", root, subdir) < 0)
- return -ENOMEM;
-
- DBG(CXT, ul_debugobj(cxt, "mount subdir %s to %s", src, target));
- if (mount(src, target, NULL, MS_BIND | MS_REC, NULL) != 0)
- rc = -MNT_ERR_APPLYFLAGS;
-
- DBG(CXT, ul_debugobj(cxt, "umount old root %s", root));
- if (umount(root) != 0)
- rc = -MNT_ERR_APPLYFLAGS;
-
- free(src);
- return rc;
-}
-
/*
* The default is to use fstype from cxt->fs, this could be overwritten by
* @try_type argument. If @try_type is specified then mount with MS_SILENT.
@@ -605,8 +582,8 @@ static int do_mount_subdir(struct libmnt_context *cxt,
*/
static int do_mount(struct libmnt_context *cxt, const char *try_type)
{
- int rc = 0, old_ns_fd = -1;
- char *org_target = NULL, *org_type = NULL;
+ int rc = 0;
+ char *org_type = NULL;
assert(cxt);
assert(cxt->fs);
@@ -642,19 +619,6 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type)
}
- /* create unhared temporary target (TODO: use MOUNT_PRE hook) */
- if (cxt->subdir) {
- org_target = strdup(mnt_fs_get_target(cxt->fs));
- if (!org_target) {
- rc = -ENOMEM;
- goto done;
- }
- rc = mnt_tmptgt_unshare(&old_ns_fd);
- if (rc)
- goto done;
- mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT);
- }
-
/*
* mount(2) or others syscalls
*/
@@ -664,24 +628,9 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type)
if (!rc)
rc = mnt_context_call_hooks(cxt, MNT_STAGE_MOUNT_POST);
- if (org_target)
- __mnt_fs_set_target_ptr(cxt->fs, org_target);
if (org_type && rc != 0)
__mnt_fs_set_fstype_ptr(cxt->fs, org_type);
- org_target = org_type = NULL;
-
- /*
- * bind subdir to the real target, umount temporary target
- * (TODO: use MOUNT_POST hook)
- */
- if (rc == 0 && cxt->subdir) {
- rc = do_mount_subdir(cxt, MNT_PATH_TMPTGT, cxt->subdir,
- mnt_fs_get_target(cxt->fs));
- if (rc)
- goto done;
- mnt_tmptgt_cleanup(old_ns_fd);
- old_ns_fd = -1;
- }
+ org_type = NULL;
if (rc == 0 && try_type && cxt->update) {
struct libmnt_fs *fs = mnt_update_get_fs(cxt->update);
@@ -690,11 +639,8 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type)
}
done:
- if (old_ns_fd >= 0)
- mnt_tmptgt_cleanup(old_ns_fd);
if (try_type)
cxt->mountflags &= ~MS_SILENT;
- free(org_target);
free(org_type);
return rc;
}
@@ -853,39 +799,6 @@ static int parse_ownership_mode(struct libmnt_context *cxt)
return 0;
}
-static int is_subdir_required(struct libmnt_context *cxt, int *rc)
-{
- char *dir;
- size_t sz;
-
- assert(cxt);
- assert(rc);
-
- *rc = 0;
-
- if (!cxt->fs
- || !cxt->fs->user_optstr
- || mnt_optstr_get_option(cxt->fs->user_optstr,
- "X-mount.subdir", &dir, &sz) != 0)
- return 0;
-
- if (dir && *dir == '"')
- dir++, sz-=2;
-
- if (!dir || sz < 1) {
- DBG(CXT, ul_debug("failed to parse X-mount.subdir '%s'", dir));
- *rc = -MNT_ERR_MOUNTOPT;
- } else {
- cxt->subdir = strndup(dir, sz);
- if (!cxt->subdir)
- *rc = -ENOMEM;
-
- DBG(CXT, ul_debug("subdir %s wanted", dir));
- }
-
- return *rc == 0;
-}
-
static int prepare_target(struct libmnt_context *cxt)
{
const char *tgt, *prefix;
@@ -929,9 +842,6 @@ static int prepare_target(struct libmnt_context *cxt)
if (!ns_old)
return -MNT_ERR_NAMESPACE;
- /* X-mount.mkdir target */
- rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_TARGET);
-
/* canonicalize the path */
if (rc == 0) {
struct libmnt_cache *cache = mnt_context_get_cache(cxt);
@@ -943,15 +853,7 @@ static int prepare_target(struct libmnt_context *cxt)
}
}
- /* X-mount.subdir= target */
- if (rc == 0
- && cxt->action == MNT_ACT_MOUNT
- && (cxt->user_mountflags & MNT_MS_XFSTABCOMM)
- && is_subdir_required(cxt, &rc)) {
-
- DBG(CXT, ul_debugobj(cxt, "subdir %s required", cxt->subdir));
- }
-
+ rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_TARGET);
if (!mnt_context_switch_ns(cxt, ns_old))
return -MNT_ERR_NAMESPACE;
diff --git a/libmount/src/hook_subdir.c b/libmount/src/hook_subdir.c
new file mode 100644
index 0000000000..7a069d0950
--- /dev/null
+++ b/libmount/src/hook_subdir.c
@@ -0,0 +1,316 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * This file is part of libmount from util-linux project.
+ *
+ * Copyright (C) 2022 Karel Zak <kzak@redhat.com>
+ *
+ * libmount is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ *
+ * This is X-mount.subdir= implementation. The code uses global hookset data
+ * rather than per-callback (hook) data.
+ */
+
+#include <sched.h>
+
+#include "mountP.h"
+#include "fileutils.h"
+
+static int hook_prepare_target(struct libmnt_context *cxt, const struct libmnt_hookset *hs, void *data);
+static int tmptgt_cleanup(int old_ns_fd);
+
+struct hookset_data {
+ char *subdir;
+ char *org_target;
+ int old_ns_fd;
+};
+
+static void free_hookset_data( struct libmnt_context *cxt,
+ const struct libmnt_hookset *hs)
+{
+ struct hookset_data *hsd = mnt_context_get_hookset_data(cxt, hs);
+
+ if (!hsd)
+ return;
+ if (hsd->old_ns_fd >= 0)
+ tmptgt_cleanup(hsd->old_ns_fd);
+
+ free(hsd->org_target);
+ free(hsd->subdir);
+ free(hsd);
+
+ mnt_context_set_hookset_data(cxt, hs, NULL);
+}
+
+/* global data, used by all callbacks */
+static struct hookset_data *new_hookset_data(
+ struct libmnt_context *cxt,
+ const struct libmnt_hookset *hs)
+{
+ struct hookset_data *hsd = calloc(1, sizeof(struct hookset_data));
+
+ if (hsd && mnt_context_set_hookset_data(cxt, hs, hsd) != 0) {
+ /* probably ENOMEM problem */
+ free(hsd);
+ hsd = NULL;
+ }
+ return hsd;
+}
+
+/* initiallize this module */
+static int hookset_init(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
+{
+ DBG(HOOK, ul_debugobj(hs, "init '%s'", hs->name));
+
+ return mnt_context_append_hook(cxt, hs,
+ MNT_STAGE_PREP_TARGET, NULL, hook_prepare_target);
+}
+
+/* de-initiallize this module */
+static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
+{
+ DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
+
+ /* remove all our hooks */
+ while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0);
+
+ /* free and remove global hookset data */
+ free_hookset_data(cxt, hs);
+
+ return 0;
+}
+
+/*
+ * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and
+ * mark (bind mount) the directory as private.
+ */
+static int tmptgt_unshare(int *old_ns_fd)
+{
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ int rc = 0, fd = -1;
+
+ assert(old_ns_fd);
+
+ *old_ns_fd = -1;
+
+ /* remember the current namespace */
+ fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ goto fail;
+
+ /* create new namespace */
+ if (unshare(CLONE_NEWNS) != 0)
+ goto fail;
+
+ /* create directory */
+ rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU);
+ if (rc)
+ goto fail;
+
+ /* try to set top-level directory as private, this is possible if
+ * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */
+ if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) {
+
+ /* failed; create a mountpoint from MNT_PATH_TMPTGT */
+ if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0)
+ goto fail;
+ if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0)
+ goto fail;
+ }
+
+ DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared"));
+ *old_ns_fd = fd;
+ return 0;
+fail:
+ if (rc == 0)
+ rc = errno ? -errno : -EINVAL;
+
+ tmptgt_cleanup(fd);
+ DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed"));
+ return rc;
+#else
+ return -ENOSYS;
+#endif
+}
+
+/*
+ * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace
+ */
+static int tmptgt_cleanup(int old_ns_fd)
+{
+#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
+ umount(MNT_PATH_TMPTGT);
+
+ if (old_ns_fd >= 0) {
+ setns(old_ns_fd, CLONE_NEWNS);
+ close(old_ns_fd);
+ }
+
+ DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done"));
+ return 0;
+#else
+ return -ENOSYS;
+#endif
+}
+
+static int do_mount_subdir(const char *root,
+ const char *subdir,
+ const char *target)
+{
+ char *src = NULL;
+ int rc = 0;
+
+ if (asprintf(&src, "%s/%s", root, subdir) < 0)
+ return -ENOMEM;
+
+ DBG(HOOK, ul_debug("mount subdir %s to %s", src, target));
+ if (mount(src, target, NULL, MS_BIND | MS_REC, NULL) != 0)
+ rc = -MNT_ERR_APPLYFLAGS;
+
+ DBG(HOOK, ul_debug("umount old root %s", root));
+ if (umount(root) != 0)
+ rc = -MNT_ERR_APPLYFLAGS;
+
+ free(src);
+ return rc;
+}
+
+
+static int hook_mount_post(
+ struct libmnt_context *cxt,
+ const struct libmnt_hookset *hs,
+ void *data __attribute__((__unused__)))
+{
+ struct hookset_data *hsd;
+ int rc = 0;
+
+ hsd = mnt_context_get_hookset_data(cxt, hs);
+ if (!hsd || !hsd->subdir)
+ return 0;
+
+ /* reset to the original mountpoint */
+ mnt_fs_set_target(cxt->fs, hsd->org_target);
+
+ /* bind subdir to the real target, umount temporary target */
+ rc = do_mount_subdir(MNT_PATH_TMPTGT,
+ hsd->subdir,
+ mnt_fs_get_target(cxt->fs));
+ if (rc)
+ return rc;
+
+ tmptgt_cleanup(hsd->old_ns_fd);
+ hsd->old_ns_fd = -1;
+
+ return rc;
+}
+
+static int hook_mount_pre(
+ struct libmnt_context *cxt,
+ const struct libmnt_hookset *hs,
+ void *data __attribute__((__unused__)))
+{
+ struct hookset_data *hsd;
+ int rc = 0;
+
+ hsd = mnt_context_get_hookset_data(cxt, hs);
+ if (!hsd)
+ return 0;
+
+ /* create unhared temporary target */
+ hsd->org_target = strdup(mnt_fs_get_target(cxt->fs));
+ if (!hsd->org_target)
+ rc = -ENOMEM;
+ if (!rc)
+ rc = tmptgt_unshare(&hsd->old_ns_fd);
+ if (!rc)
+ mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT);
+ if (!rc)
+ rc = mnt_context_append_hook(cxt, hs,
+ MNT_STAGE_MOUNT_POST,
+ NULL, hook_mount_post);
+ return rc;
+}
+
+
+
+static int is_subdir_required(struct libmnt_context *cxt, int *rc, char **subdir)
+{
+ char *dir = NULL;
+ size_t sz;
+
+ assert(cxt);
+ assert(rc);
+
+ *rc = 0;
+
+ if (!cxt->fs
+ || !cxt->fs->user_optstr
+ || mnt_optstr_get_option(cxt->fs->user_optstr,
+ "X-mount.subdir", &dir, &sz) != 0)
+ return 0;
+
+ if (dir && *dir == '"')
+ dir++, sz-=2;
+
+ if (!dir || sz < 1) {
+ DBG(HOOK, ul_debug("failed to parse X-mount.subdir '%s'", dir));
+ *rc = -MNT_ERR_MOUNTOPT;
+ } else {
+ *subdir = strndup(dir, sz);
+ if (!*subdir)
+ *rc = -ENOMEM;
+ }
+
+ return *rc == 0;
+}
+
+/* this is the initial callback used to check mount options and define next
+ * actions if necessary */
+static int hook_prepare_target(
+ struct libmnt_context *cxt,
+ const struct libmnt_hookset *hs,
+ void *data __attribute__((__unused__)))
+{
+ const char *tgt;
+ char *subdir = NULL;
+ int rc = 0;
+
+ assert(cxt);
+
+ tgt = mnt_fs_get_target(cxt->fs);
+ if (!tgt)
+ return 0;
+
+ /* X-mount.subdir= target */
+ if (cxt->action == MNT_ACT_MOUNT
+ && (cxt->user_mountflags & MNT_MS_XFSTABCOMM)
+ && is_subdir_required(cxt, &rc, &subdir)) {
+
+ /* create a global data */
+ struct hookset_data *hsd = new_hookset_data(cxt, hs);
+
+ if (!hsd)
+ return -ENOMEM;
+
+ hsd->subdir = subdir;
+
+ DBG(HOOK, ul_debugobj(hs, "subdir %s wanted", subdir));
+
+ rc = mnt_context_append_hook(cxt, hs,
+ MNT_STAGE_MOUNT_PRE,
+ NULL, hook_mount_pre);
+ }
+
+ return rc;
+}
+
+
+const struct libmnt_hookset hookset_subdir =
+{
+ .name = "__subdir",
+ .init = hookset_init,
+ .deinit = hookset_deinit
+};
diff --git a/libmount/src/hooks.c b/libmount/src/hooks.c
index 5e6327c6e5..13a3185993 100644
--- a/libmount/src/hooks.c
+++ b/libmount/src/hooks.c
@@ -8,12 +8,14 @@
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
- */
-
-/**
- * SECTION: hookset
- * @title: Mount context hooks
- * @short_description: extensions to mount process
+ *
+ *
+ * The "hookset" is set of callbacks (hooks) that implement some functionality.
+ * It supports two kinds of data:
+ *
+ * - global data : accessible for all callbacks, independent on defined hooks
+ *
+ * - per-hook data : usually used by the callback function
*/
#include "mountP.h"
@@ -23,6 +25,7 @@ static const struct libmnt_hookset *hooksets[] =
{
#ifdef __linux__
&hookset_mkdir,
+ &hookset_subdir,
&hookset_mount_legacy
#endif
};
@@ -151,6 +154,7 @@ int mnt_context_set_hookset_data(struct libmnt_context *cxt,
/* deallocate old data */
if (data == NULL) {
if (hd) {
+ DBG(CXT, ul_debugobj(cxt, " free '%s' data", hs->name));
list_del(&hd->datas);
free(hd);
}
@@ -163,7 +167,7 @@ int mnt_context_set_hookset_data(struct libmnt_context *cxt,
if (!hd)
return -ENOMEM;
- DBG(CXT, ul_debugobj(cxt, "alloc '%s' data", hs->name));
+ DBG(CXT, ul_debugobj(cxt, " alloc '%s' data", hs->name));
INIT_LIST_HEAD(&hd->datas);
hd->hookset = hs;
list_add_tail(&hd->datas, &cxt->hooksets_datas);
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 29dcba82fa..80f02af761 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -302,6 +302,7 @@ struct libmnt_hookset {
/* built-in hooks */
extern const struct libmnt_hookset hookset_mount_legacy;
extern const struct libmnt_hookset hookset_mkdir;
+extern const struct libmnt_hookset hookset_subdir;
extern int mnt_context_init_hooksets(struct libmnt_context *cxt);
@@ -353,8 +354,6 @@ struct libmnt_context
char *fstype_pattern; /* for mnt_match_fstype() */
char *optstr_pattern; /* for mnt_match_options() */
- char *subdir; /* X-mount.subdir= */
-
uid_t tgt_owner; /* X-mount.owner= */
gid_t tgt_group; /* X-mount.group= */
mode_t tgt_mode; /* X-mount.mode= */
diff --git a/libmount/src/utils.c b/libmount/src/utils.c
index 5693cf8adf..26c5aef7b1 100644
--- a/libmount/src/utils.c
+++ b/libmount/src/utils.c
@@ -1240,79 +1240,6 @@ done:
return 1;
}
-/*
- * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and
- * mark (bind mount) the directory as private.
- */
-int mnt_tmptgt_unshare(int *old_ns_fd)
-{
-#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
- int rc = 0, fd = -1;
-
- assert(old_ns_fd);
-
- *old_ns_fd = -1;
-
- /* remember the current namespace */
- fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
- if (fd < 0)
- goto fail;
-
- /* create new namespace */
- if (unshare(CLONE_NEWNS) != 0)
- goto fail;
-
- /* create directory */
- rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU);
- if (rc)
- goto fail;
-
- /* try to set top-level directory as private, this is possible if
- * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */
- if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) {
-
- /* failed; create a mountpoint from MNT_PATH_TMPTGT */
- if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0)
- goto fail;
- if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0)
- goto fail;
- }
-
- DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared"));
- *old_ns_fd = fd;
- return 0;
-fail:
- if (rc == 0)
- rc = errno ? -errno : -EINVAL;
-
- mnt_tmptgt_cleanup(fd);
- DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed"));
- return rc;
-#else
- return -ENOSYS;
-#endif
-}
-
-/*
- * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace
- */
-int mnt_tmptgt_cleanup(int old_ns_fd)
-{
-#ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
- umount(MNT_PATH_TMPTGT);
-
- if (old_ns_fd >= 0) {
- setns(old_ns_fd, CLONE_NEWNS);
- close(old_ns_fd);
- }
-
- DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done"));
- return 0;
-#else
- return -ENOSYS;
-#endif
-}
-
#ifdef TEST_PROGRAM
static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
{