diff --git a/doc/man1/flux-kvs.adoc b/doc/man1/flux-kvs.adoc index 5b063b13839a..a426eb07a55e 100644 --- a/doc/man1/flux-kvs.adoc +++ b/doc/man1/flux-kvs.adoc @@ -117,15 +117,20 @@ Remove 'key' from the KVS and commit the change. If 'key' represents a directory, specify '-R' to remove all keys underneath it. If '-f' is specified, ignore nonexistent files. -*link* 'target' 'linkname':: +*link* [-T ns] 'target' 'linkname':: Create a new name for 'target', similar to a symbolic link, and commit the change. 'target' does not have to exist. If 'linkname' exists, -it is overwritten. +it is overwritten. Optionally, specify a namespace the target is in. +This namespace can be different that the one the linkname is being +written to. -*readlink* [-a treeobj] 'key' ['key...']:: +*readlink* [-a treeobj] [ -o | -k ] 'key' ['key...']:: Retrieve the key a link refers to rather than its value, as would be returned by *get*. '-a treeobj' causes the lookup to be relative to -an RFC 11 snapshot reference. +an RFC 11 snapshot reference. If the link points to a namespace, the +namespace and key will be output in the format '::'. +The '-o' can be used to only output namespaces and the '-k' can be +used to only output keys. *mkdir* 'key' ['key...']:: Create an empty directory and commit the change. If 'key' exists, diff --git a/doc/man3/flux_kvs_lookup.adoc b/doc/man3/flux_kvs_lookup.adoc index 9aeda77f8ac0..da93db806a15 100644 --- a/doc/man3/flux_kvs_lookup.adoc +++ b/doc/man3/flux_kvs_lookup.adoc @@ -29,7 +29,8 @@ SYNOPSIS int flux_kvs_lookup_get_treeobj (flux_future_t *f, const char **treeobj); - int flux_kvs_lookup_get_symlink (flux_future_t *f, const char **target); + int flux_kvs_lookup_get_symlink (flux_future_t *f, const char **ns, + const char **target); const char *flux_kvs_lookup_get_key (flux_future_t *f); @@ -87,7 +88,9 @@ function should work on all. `flux_kvs_lookup_get_symlink()` interprets the result as a symlink target, e.g. in response to a lookup with the FLUX_KVS_READLINK flag set. -The result is parsed and symlink target is assigned to _target_. +The result is parsed and symlink namespace is assigned to _ns_ and +the symlink target is assigned to _target_. If a namespace was not assigned +to the symlink, _ns_ is set to NULL. `flux_kvs_lookup_get_key()` accesses the key argument from the original lookup. diff --git a/doc/man3/flux_kvs_txn_create.adoc b/doc/man3/flux_kvs_txn_create.adoc index cf06c3f56773..62daee20458b 100644 --- a/doc/man3/flux_kvs_txn_create.adoc +++ b/doc/man3/flux_kvs_txn_create.adoc @@ -32,7 +32,8 @@ SYNOPSIS const char *key); int flux_kvs_txn_symlink (flux_kvs_txn_t *txn, int flags, - const char *key, const char *target); + const char *key, const char *ns, + const char *target); int flux_kvs_txn_put_raw (flux_kvs_txn_t *txn, int flags, const char *key, const void *data, int len); @@ -71,8 +72,10 @@ from a JSON object built with `json_pack()` style arguments (see below). `flux_kvs_txn_unlink()` removes _key_. If _key_ is a directory, all its contents are removed as well. -`flux_kvs_txn_symlink()` sets _key_ to a symbolic link pointing to _target_, -another key. _target_ need not exist. +`flux_kvs_txn_symlink()` sets _key_ to a symbolic link pointing to a +namespace _ns_ and a _target_ key within that namespace. Neither _ns_ +nor _target_ must exist. The namespace _ns_ is optional, if set to +NULL the _target_ is assumed to be in the key's current namespace. `flux_kvs_txn_put_raw()` sets _key_ to a value containing raw data referred to by _data_ of length _len_. diff --git a/src/bindings/lua/flux-lua.c b/src/bindings/lua/flux-lua.c index 50044fa01a03..6876279d9ffb 100644 --- a/src/bindings/lua/flux-lua.c +++ b/src/bindings/lua/flux-lua.c @@ -318,7 +318,7 @@ static int l_flux_kvs_type (lua_State *L) return lua_pusherror (L, "key expected in arg #2"); if ((future = flux_kvs_lookup (f, FLUX_KVS_READLINK, key)) - && flux_kvs_lookup_get_symlink (future, &target) == 0) { + && flux_kvs_lookup_get_symlink (future, NULL, &target) == 0) { lua_pushstring (L, "symlink"); lua_pushstring (L, target); flux_future_destroy (future); diff --git a/src/bindings/python/flux/kvs.py b/src/bindings/python/flux/kvs.py index dcc016cac1fc..8ae4e4140014 100644 --- a/src/bindings/python/flux/kvs.py +++ b/src/bindings/python/flux/kvs.py @@ -99,7 +99,7 @@ def put_unlink(flux_handle, key): def put_symlink(flux_handle, key, target): if flux_handle.aux_txn is None: flux_handle.aux_txn = RAW.flux_kvs_txn_create() - return RAW.flux_kvs_txn_symlink(flux_handle.aux_txn, 0, key, target) + return RAW.flux_kvs_txn_symlink(flux_handle.aux_txn, 0, key, None, target) def commit(flux_handle, flags=0): diff --git a/src/cmd/flux-kvs.c b/src/cmd/flux-kvs.c index a1c82a732d81..e712c773c853 100644 --- a/src/cmd/flux-kvs.c +++ b/src/cmd/flux-kvs.c @@ -68,6 +68,12 @@ static struct optparse_option readlink_opts[] = { { .name = "at", .key = 'a', .has_arg = 1, .usage = "Lookup relative to RFC 11 snapshot reference", }, + { .name = "namespace-only", .key = 'o', .has_arg = 0, + .usage = "Output only namespace if link points to a namespace", + }, + { .name = "key-only", .key = 'k', .has_arg = 0, + .usage = "Output only key if link points to a namespace", + }, OPTPARSE_TABLE_END }; @@ -215,6 +221,13 @@ static struct optparse_option copy_opts[] = { OPTPARSE_TABLE_END }; +static struct optparse_option link_opts[] = { + { .name = "target-namespace", .key = 'T', .has_arg = 1, + .usage = "Specify target's namespace", + }, + OPTPARSE_TABLE_END +}; + static struct optparse_subcommand subcommands[] = { { "namespace-create", "name [name...]", @@ -273,14 +286,14 @@ static struct optparse_subcommand subcommands[] = { unlink_opts }, { "link", - "target linkname", + "[-T ns] target linkname", "Create a new name for target", cmd_link, 0, - NULL + link_opts }, { "readlink", - "[-a treeobj] key [key...]", + "[-a treeobj] [ -o | -k ] key [key...]", "Retrieve the key a link refers to", cmd_readlink, 0, @@ -911,6 +924,8 @@ int cmd_unlink (optparse_t *p, int argc, char **argv) int cmd_link (optparse_t *p, int argc, char **argv) { flux_t *h = (flux_t *)optparse_get_data (p, "flux_handle"); + const char *ns = NULL; + const char *target, *linkname; int optindex; flux_kvs_txn_t *txn; flux_future_t *f; @@ -923,10 +938,14 @@ int cmd_link (optparse_t *p, int argc, char **argv) if (optindex != (argc - 2)) log_msg_exit ("link: specify target and link_name"); + ns = optparse_get_str (p, "target-namespace", NULL); + target = argv[optindex]; + linkname = argv[optindex + 1]; + if (!(txn = flux_kvs_txn_create ())) log_err_exit ("flux_kvs_txn_create"); - if (flux_kvs_txn_symlink (txn, 0, argv[optindex + 1], argv[optindex]) < 0) - log_err_exit ("%s", argv[optindex + 1]); + if (flux_kvs_txn_symlink (txn, 0, linkname, ns, target) < 0) + log_err_exit ("%s", linkname); if (!(f = flux_kvs_commit (h, 0, txn)) || flux_future_get (f, NULL) < 0) log_err_exit ("flux_kvs_commit"); flux_future_destroy (f); @@ -938,16 +957,21 @@ int cmd_readlink (optparse_t *p, int argc, char **argv) { flux_t *h = (flux_t *)optparse_get_data (p, "flux_handle"); int optindex, i; - const char *target; flux_future_t *f; + bool ns_only; + bool key_only; optindex = optparse_option_index (p); if ((optindex - argc) == 0) { optparse_print_usage (p); exit (1); } + ns_only = optparse_hasopt (p, "namespace-only"); + key_only = optparse_hasopt (p, "key-only"); for (i = optindex; i < argc; i++) { + const char *ns = NULL; + const char *target = NULL; if (optparse_hasopt (p, "at")) { const char *ref = optparse_get_str (p, "at", ""); if (!(f = flux_kvs_lookupat (h, FLUX_KVS_READLINK, argv[i], ref))) @@ -957,9 +981,18 @@ int cmd_readlink (optparse_t *p, int argc, char **argv) if (!(f = flux_kvs_lookup (h, FLUX_KVS_READLINK, argv[i]))) log_err_exit ("%s", argv[i]); } - if (flux_kvs_lookup_get_symlink (f, &target) < 0) + if (flux_kvs_lookup_get_symlink (f, &ns, &target) < 0) log_err_exit ("%s", argv[i]); - printf ("%s\n", target); + if (ns_only && ns) + printf ("%s\n", ns); + else if (key_only) + printf ("%s\n", target); + else { + if (ns) + printf ("%s::%s\n", ns, target); + else + printf ("%s\n", target); + } flux_future_destroy (f); } return (0); @@ -1241,13 +1274,16 @@ static void dump_kvs_dir (const flux_kvsdir_t *dir, int maxcol, while ((name = flux_kvsitr_next (itr))) { key = flux_kvsdir_key_at (dir, name); if (flux_kvsdir_issymlink (dir, name)) { - const char *link; + const char *ns = NULL; + const char *target = NULL; if (!(f = flux_kvs_lookupat (h, FLUX_KVS_READLINK, key, rootref)) - || flux_kvs_lookup_get_symlink (f, &link) < 0) + || flux_kvs_lookup_get_symlink (f, &ns, &target) < 0) log_err_exit ("%s", key); - printf ("%s -> %s\n", key, link); + if (ns) + printf ("%s -> %s::%s\n", key, ns, target); + else + printf ("%s -> %s\n", key, target); flux_future_destroy (f); - } else if (flux_kvsdir_isdir (dir, name)) { if (Ropt) { const flux_kvsdir_t *ndir; diff --git a/src/common/libkvs/kvs_classic.c b/src/common/libkvs/kvs_classic.c index 16ce9b9902c0..bde022a4705c 100644 --- a/src/common/libkvs/kvs_classic.c +++ b/src/common/libkvs/kvs_classic.c @@ -244,7 +244,7 @@ int flux_kvs_symlink (flux_t *h, const char *key, const char *target) flux_kvs_txn_t *txn = get_default_txn (h); if (!txn) return -1; - return flux_kvs_txn_symlink (txn, 0, key, target); + return flux_kvs_txn_symlink (txn, 0, key, NULL, target); } int flux_kvs_mkdir (flux_t *h, const char *key) diff --git a/src/common/libkvs/kvs_lookup.c b/src/common/libkvs/kvs_lookup.c index fad193b508a9..f93b8e561da5 100644 --- a/src/common/libkvs/kvs_lookup.c +++ b/src/common/libkvs/kvs_lookup.c @@ -372,11 +372,12 @@ int flux_kvs_lookup_get_dir (flux_future_t *f, const flux_kvsdir_t **dirp) return 0; } -int flux_kvs_lookup_get_symlink (flux_future_t *f, const char **target) +int flux_kvs_lookup_get_symlink (flux_future_t *f, + const char **ns, + const char **target) { struct lookup_ctx *ctx; - json_t *str; - const char *s; + const char *n = NULL, *t = NULL; if (!(ctx = get_lookup_ctx (f))) return -1; @@ -386,13 +387,12 @@ int flux_kvs_lookup_get_symlink (flux_future_t *f, const char **target) errno = EINVAL; return -1; } - if (!(str = treeobj_get_data (ctx->treeobj)) - || !(s = json_string_value (str))) { - errno = EINVAL; + if (treeobj_get_symlink (ctx->treeobj, &n, &t) < 0) return -1; - } + if (ns) + *ns = n; if (target) - *target = s; + *target = t; return 0; } diff --git a/src/common/libkvs/kvs_lookup.h b/src/common/libkvs/kvs_lookup.h index d3967ecb8b42..ad0d479d727e 100644 --- a/src/common/libkvs/kvs_lookup.h +++ b/src/common/libkvs/kvs_lookup.h @@ -24,7 +24,9 @@ int flux_kvs_lookup_get_unpack (flux_future_t *f, const char *fmt, ...); int flux_kvs_lookup_get_raw (flux_future_t *f, const void **data, int *len); int flux_kvs_lookup_get_treeobj (flux_future_t *f, const char **treeobj); int flux_kvs_lookup_get_dir (flux_future_t *f, const flux_kvsdir_t **dir); -int flux_kvs_lookup_get_symlink (flux_future_t *f, const char **target); +int flux_kvs_lookup_get_symlink (flux_future_t *f, + const char **ns, + const char **target); const char *flux_kvs_lookup_get_key (flux_future_t *f); diff --git a/src/common/libkvs/kvs_txn.c b/src/common/libkvs/kvs_txn.c index f7438d82dbde..5488e94eb796 100644 --- a/src/common/libkvs/kvs_txn.c +++ b/src/common/libkvs/kvs_txn.c @@ -285,7 +285,8 @@ int flux_kvs_txn_unlink (flux_kvs_txn_t *txn, int flags, } int flux_kvs_txn_symlink (flux_kvs_txn_t *txn, int flags, - const char *key, const char *target) + const char *key, const char *ns, + const char *target) { json_t *dirent = NULL; int saved_errno; @@ -296,7 +297,7 @@ int flux_kvs_txn_symlink (flux_kvs_txn_t *txn, int flags, } if (validate_flags (flags, 0) < 0) goto error; - if (!(dirent = treeobj_create_symlink (target))) + if (!(dirent = treeobj_create_symlink (ns, target))) goto error; if (append_op_to_txn (txn, flags, key, dirent) < 0) goto error; diff --git a/src/common/libkvs/kvs_txn.h b/src/common/libkvs/kvs_txn.h index 7a1130f98e65..89217d868ee1 100644 --- a/src/common/libkvs/kvs_txn.h +++ b/src/common/libkvs/kvs_txn.h @@ -44,7 +44,8 @@ int flux_kvs_txn_unlink (flux_kvs_txn_t *txn, int flags, const char *key); int flux_kvs_txn_symlink (flux_kvs_txn_t *txn, int flags, - const char *key, const char *target); + const char *key, const char *ns, + const char *target); #ifdef __cplusplus } diff --git a/src/common/libkvs/test/kvs_dir.c b/src/common/libkvs/test/kvs_dir.c index d12012d2638a..23f4e507e8f3 100644 --- a/src/common/libkvs/test/kvs_dir.c +++ b/src/common/libkvs/test/kvs_dir.c @@ -135,8 +135,8 @@ void test_full (void) if (!(o = treeobj_create_dir ())) BAIL_OUT ("treeobj_create_dir failed"); - if (!(dirent = treeobj_create_symlink ("a.b.c"))) - BAIL_OUT ("treeobj_create_symlink failed"); + if (!(dirent = treeobj_create_symlink (NULL, "a.b.c"))) + BAIL_OUT ("treeobj_create_symlink failed (no namespace)"); if (treeobj_insert_entry (o, "foo", dirent) < 0) BAIL_OUT ("treeobj_insert_entry failed"); json_decref (dirent); @@ -150,6 +150,11 @@ void test_full (void) if (treeobj_insert_entry (o, "baz", dirent) < 0) BAIL_OUT ("treeobj_insert_entry failed"); json_decref (dirent); + if (!(dirent = treeobj_create_symlink ("ns", "d.e.f"))) + BAIL_OUT ("treeobj_create_symlink failed (namespace)"); + if (treeobj_insert_entry (o, "boo", dirent) < 0) + BAIL_OUT ("treeobj_insert_entry failed"); + json_decref (dirent); if (!(s = json_dumps (o, JSON_COMPACT))) BAIL_OUT ("json_dumps failed on new treeobj"); @@ -167,6 +172,8 @@ void test_full (void) "flux_kvsdir_exists on existing val returns true"); ok (flux_kvsdir_exists (dir, "baz"), "flux_kvsdir_exists on existing dir returns true"); + ok (flux_kvsdir_exists (dir, "boo"), + "flux_kvsdir_exists on existing dir returns true"); ok (!flux_kvsdir_isdir (dir, "noexist"), "flux_kvsdir_isdir on nonexistent key returns false"); @@ -176,6 +183,8 @@ void test_full (void) "flux_kvsdir_isdir on existing val returns false"); ok (flux_kvsdir_isdir (dir, "baz"), "flux_kvsdir_isdir on existing symlink returns true"); + ok (!flux_kvsdir_isdir (dir, "boo"), + "flux_kvsdir_isdir on existing symlink returns false"); ok (!flux_kvsdir_issymlink (dir, "noexist"), "flux_kvsdir_issymlink on nonexistent key returns false"); @@ -185,9 +194,11 @@ void test_full (void) "flux_kvsdir_issymlink on existing val returns false"); ok (!flux_kvsdir_issymlink (dir, "baz"), "flux_kvsdir_issymlink on existing dir returns false"); + ok (flux_kvsdir_issymlink (dir, "boo"), + "flux_kvsdir_issymlink on existing dir returns false"); - ok (flux_kvsdir_get_size (dir) == 3, - "flux_kvsdir_get_size returns 3"); + ok (flux_kvsdir_get_size (dir) == 4, + "flux_kvsdir_get_size returns 4"); itr = flux_kvsitr_create (dir); ok (itr != NULL, @@ -198,8 +209,10 @@ void test_full (void) "flux_kvsitr_next returns non-NULL on second call"); ok (flux_kvsitr_next (itr) != NULL, "flux_kvsitr_next returns non-NULL on third call"); - ok (flux_kvsitr_next (itr) == NULL, + ok (flux_kvsitr_next (itr) != NULL, "flux_kvsitr_next returns NULL on fourth call"); + ok (flux_kvsitr_next (itr) == NULL, + "flux_kvsitr_next returns NULL on fifth call"); flux_kvsitr_rewind (itr); ok (flux_kvsitr_next (itr) != NULL, @@ -208,20 +221,22 @@ void test_full (void) "flux_kvsitr_next returns non-NULL on second call"); ok (flux_kvsitr_next (itr) != NULL, "flux_kvsitr_next returns non-NULL on third call"); - ok (flux_kvsitr_next (itr) == NULL, + ok (flux_kvsitr_next (itr) != NULL, "flux_kvsitr_next returns NULL on fourth call"); + ok (flux_kvsitr_next (itr) == NULL, + "flux_kvsitr_next returns NULL on fifth call"); flux_kvsitr_destroy (itr); flux_kvsdir_t *cpy = flux_kvsdir_copy (dir); ok (cpy != NULL, "flux_kvsdir_copy was successful"); - ok (flux_kvsdir_get_size (cpy) == 3, - "flux_kvsdir_get_size on copy returns 3"); + ok (flux_kvsdir_get_size (cpy) == 4, + "flux_kvsdir_get_size on copy returns 4"); flux_kvsdir_destroy (dir); - ok (flux_kvsdir_get_size (cpy) == 3, - "flux_kvsdir_get_size on copy still returns 3 after orig freed"); + ok (flux_kvsdir_get_size (cpy) == 4, + "flux_kvsdir_get_size on copy still returns 4 after orig freed"); flux_kvsdir_destroy (cpy); } diff --git a/src/common/libkvs/test/kvs_txn.c b/src/common/libkvs/test/kvs_txn.c index df336d787898..6ad1069c28cc 100644 --- a/src/common/libkvs/test/kvs_txn.c +++ b/src/common/libkvs/test/kvs_txn.c @@ -141,9 +141,9 @@ void basic (void) rc = flux_kvs_txn_mkdir (txn, 0, "b.b.b"); ok (rc == 0, "4: flux_kvs_txn_mkdir works"); - rc = flux_kvs_txn_symlink (txn, 0, "c.c.c", "b.b.b"); + rc = flux_kvs_txn_symlink (txn, 0, "c.c.c", NULL, "b.b.b"); ok (rc == 0, - "5: flux_kvs_txn_symlink works"); + "5: flux_kvs_txn_symlink works (no namespace)"); rc = flux_kvs_txn_put (txn, 0, "d.d.d", "43"); ok (rc == 0, "6: flux_kvs_txn_put(i) works"); @@ -153,6 +153,9 @@ void basic (void) rc = flux_kvs_txn_put (txn, 0, "nerrrrb", NULL); ok (rc == 0, "8: flux_kvs_txn_put(NULL) works"); + rc = flux_kvs_txn_symlink (txn, 0, "f.f.f", "g.g.g", "h.h.h"); + ok (rc == 0, + "9: flux_kvs_txn_symlink works (namespace)"); errno = 0; rc = flux_kvs_txn_pack (txn, 0xFFFF, "foo.bar.blorp", "s", "foo"); ok (rc < 0 && errno == EINVAL, @@ -168,8 +171,8 @@ void basic (void) /* Verify transaction contents */ - ok (txn_get_op_count (txn) == 8, - "txn contains 8 ops"); + ok (txn_get_op_count (txn) == 9, + "txn contains 9 ops"); ok (txn_get_op (txn, 0, &entry) == 0 && entry != NULL, "1: retrieved"); @@ -218,8 +221,12 @@ void basic (void) ok (!strcmp (key, "c.c.c") && flags == 0 && treeobj_is_symlink (dirent) - && !strcmp (json_string_value (treeobj_get_data (dirent)), "b.b.b"), - "5: symlink c.c.c b.b.b"); + && !json_object_get (treeobj_get_data (dirent), + "namespace") + && !strcmp (json_string_value (json_object_get (treeobj_get_data (dirent), + "target")), + "b.b.b"), + "5: symlink c.c.c b.b.b (no namespace)"); ok (txn_get_op (txn, 5, &entry) == 0 && entry != NULL, "6: retrieved"); @@ -251,9 +258,24 @@ void basic (void) && check_null_value (dirent) == 0, "8: put nerrrrb = NULL"); + ok (txn_get_op (txn, 8, &entry) == 0 && entry != NULL, + "9: retrieved"); + jdiag (entry); + ok (txn_decode_op (entry, &key, &flags, &dirent) == 0, + "9: txn_decode_op works"); + ok (!strcmp (key, "f.f.f") + && flags == 0 + && !strcmp (json_string_value (json_object_get (treeobj_get_data (dirent), + "namespace")), + "g.g.g") + && !strcmp (json_string_value (json_object_get (treeobj_get_data (dirent), + "target")), + "h.h.h"), + "9: symlink f.f.f g.g.g h.h.h (namespace)"); + errno = 0; - ok (txn_get_op (txn, 8, &entry) < 0 && errno == EINVAL, - "9: invalid (end of transaction)"); + ok (txn_get_op (txn, 9, &entry) < 0 && errno == EINVAL, + "10: invalid (end of transaction)"); flux_kvs_txn_destroy (txn); } diff --git a/src/common/libkvs/test/treeobj.c b/src/common/libkvs/test/treeobj.c index ba065d00b5e0..4862baa32377 100644 --- a/src/common/libkvs/test/treeobj.c +++ b/src/common/libkvs/test/treeobj.c @@ -28,7 +28,7 @@ json_t *create_large_dir (void) return NULL; for (i = 0; i < large_dir_entries; i++) { snprintf (name, sizeof (name), "entry-%.10d", i); - if (!(ent = treeobj_create_symlink ("a.b.c.d")) + if (!(ent = treeobj_create_symlink (NULL, "a.b.c.d")) || treeobj_insert_entry (dir, name, ent) < 0) { json_decref (dir); return NULL; @@ -421,9 +421,24 @@ void test_copy (void) json_decref (val); json_decref (valcpy); - /* Test symlink copy */ + /* Test symlink copy (no namespace) */ - symlink = treeobj_create_symlink ("abcdefgh"); + symlink = treeobj_create_symlink (NULL, "abcdefgh"); + if (!symlink) + BAIL_OUT ("can't continue without test symlink"); + + ok ((symlinkcpy = treeobj_copy (symlink)) != NULL, + "treeobj_copy worked on symlink"); + + ok (symlink != symlinkcpy && json_equal (symlink, symlinkcpy) == 1, + "treeobj_copy returned duplicate symlink copy"); + + json_decref (symlink); + json_decref (symlinkcpy); + + /* Test symlink copy (with namespace) */ + + symlink = treeobj_create_symlink ("foo-namespace", "abcdefgh"); if (!symlink) BAIL_OUT ("can't continue without test symlink"); @@ -636,27 +651,45 @@ void test_deep_copy (void) void test_symlink (void) { json_t *o, *data; - const char *str; + const char *ns_str, *target_str; - ok (treeobj_create_symlink (NULL) == NULL + ok (treeobj_create_symlink (NULL, NULL) == NULL && errno == EINVAL, "treeobj_create_symlink fails on bad input with EINVAL"); - o = treeobj_create_symlink ("a.b.c"); + o = treeobj_create_symlink (NULL, "a.b.c"); ok (o != NULL, "treeobj_create_symlink works"); diag_json (o); ok (treeobj_is_symlink (o), "treeobj_is_symlink returns true"); - ok ((data = treeobj_get_data (o)) != NULL && json_is_string (data), + ok ((data = treeobj_get_data (o)) != NULL && json_is_object (data), "treeobj_get_data returned string"); - ok (!strcmp (json_string_value (data), "a.b.c"), - "and string has right content"); - ok (treeobj_get_symlink (NULL) == NULL, + ok (treeobj_get_symlink (NULL, NULL, NULL) < 0, "treeobj_get_symlink fails on bad input"); - ok ((str = treeobj_get_symlink (o)) != NULL, - "treeobj_get_symlink works"); - ok (!strcmp (str, "a.b.c"), - "treeobj_get_symlink returns correct string"); + ok (treeobj_get_symlink (o, &ns_str, &target_str) == 0, + "treeobj_get_symlink works on symlink without namespace"); + ok (ns_str == NULL, + "treeobj_get_symlink returns NULL for namespace"); + ok (!strcmp (target_str, "a.b.c"), + "treeobj_get_symlink returns correct string for target"); + + json_decref (o); + + o = treeobj_create_symlink ("ns", "d.e.f"); + ok (o != NULL, + "treeobj_create_symlink works"); + diag_json (o); + ok (treeobj_is_symlink (o), + "treeobj_is_symlink returns true"); + ok ((data = treeobj_get_data (o)) != NULL && json_is_object (data), + "treeobj_get_data returned string"); + ok (treeobj_get_symlink (o, &ns_str, &target_str) == 0, + "treeobj_get_symlink works on symlink with namespace"); + ok (!strcmp (ns_str, "ns"), + "treeobj_get_symlink returns correct string for namespace"); + ok (!strcmp (target_str, "d.e.f"), + "treeobj_get_symlink returns correct string for target"); + json_decref (o); } @@ -739,7 +772,7 @@ void test_corner_cases (void) json_decref (dir); - symlink = treeobj_create_symlink ("some-string"); + symlink = treeobj_create_symlink (NULL, "some-string"); if (!symlink) BAIL_OUT ("can't continue without test value"); diff --git a/src/common/libkvs/treeobj.c b/src/common/libkvs/treeobj.c index 86541ed86dc8..7dbcb214e411 100644 --- a/src/common/libkvs/treeobj.c +++ b/src/common/libkvs/treeobj.c @@ -102,8 +102,18 @@ int treeobj_validate (const json_t *obj) } } else if (!strcmp (type, "symlink")) { - if (!json_is_string (data)) + json_t *o; + if (!json_is_object (data)) + goto inval; + if (!(o = json_object_get (data, "target"))) + goto inval; + if (!json_is_string (o)) goto inval; + /* namespace is optional, not an error if doesn't exist */ + if ((o = json_object_get (data, "namespace"))) { + if (!json_is_string (o)) + goto inval; + } } else if (!strcmp (type, "val")) { /* is base64, should always be a string */ @@ -164,22 +174,40 @@ json_t *treeobj_get_data (json_t *obj) return data; } -const char *treeobj_get_symlink (const json_t *obj) +int treeobj_get_symlink (const json_t *obj, + const char **ns, + const char **target) { const char *type; const json_t *data; - const char *str; + const json_t *n, *t; + const char *n_str = NULL, *t_str = NULL; if (treeobj_peek (obj, &type, &data) < 0 || strcmp (type, "symlink") != 0) { errno = EINVAL; - return NULL; + return -1; } - if (!(str = json_string_value (data))) { + /* namespace is optional, not an error if it doesn't exist */ + if ((n = json_object_get (data, "namespace"))) { + if (!(n_str = json_string_value (n))) { + errno = EINVAL; + return -1; + } + } + if (!(t = json_object_get (data, "target"))) { errno = EINVAL; - return NULL; + return -1; } - return str; + if (!(t_str = json_string_value (t))) { + errno = EINVAL; + return -1; + } + if (ns) + (*ns) = n_str; + if (target) + (*target) = t_str; + return 0; } int treeobj_decode_val (const json_t *obj, void **dp, int *lp) @@ -420,21 +448,38 @@ json_t *treeobj_create_dir (void) return obj; } -json_t *treeobj_create_symlink (const char *target) +json_t *treeobj_create_symlink (const char *ns, const char *target) { - json_t *obj; + json_t *data, *obj; if (!target) { errno = EINVAL; return NULL; } - if (!(obj = json_pack ("{s:i s:s s:s}", "ver", treeobj_version, + if (!ns) { + if (!(data = json_pack ("{s:s}", "target", target))) { + errno = ENOMEM; + return NULL; + } + } + else { + if (!(data = json_pack ("{s:s s:s}", "namespace", ns, + "target", target))) { + errno = ENOMEM; + return NULL; + } + } + + /* obj takes reference to "data" */ + if (!(obj = json_pack ("{s:i s:s s:o}", "ver", treeobj_version, "type", "symlink", - "data", target))) { + "data", data))) { + json_decref (data); errno = ENOMEM; return NULL; } + return obj; } diff --git a/src/common/libkvs/treeobj.h b/src/common/libkvs/treeobj.h index c1a9afb3056b..122c7c503bc9 100644 --- a/src/common/libkvs/treeobj.h +++ b/src/common/libkvs/treeobj.h @@ -20,9 +20,10 @@ * valref, dirref: if blobref is NULL, treeobj_append_blobref() * must be called before object is valid. * val: copies argument (caller retains ownership) + * symlink: ns argument is optional * Return JSON object on success, NULL on failure with errno set. */ -json_t *treeobj_create_symlink (const char *target); +json_t *treeobj_create_symlink (const char *ns, const char *target); json_t *treeobj_create_val (const void *data, int len); json_t *treeobj_create_valref (const char *blobref); json_t *treeobj_create_dir (void); @@ -48,7 +49,7 @@ bool treeobj_is_dirref (const json_t *obj); /* get type-specific value. * For dirref/valref, this is an array of blobrefs. * For directory, this is dictionary of treeobjs - * For symlink, this is a string. + * For symlink, this is an object with optinoal namespace and target. * For val this is string containing base64-encoded data. * Return JSON object on success, NULL on error with errno = EINVAL. * The returned object is owned by 'obj' and must not be destroyed. @@ -57,7 +58,9 @@ json_t *treeobj_get_data (json_t *obj); /* get convenience functions, operate on type specific objects */ -const char *treeobj_get_symlink (const json_t *obj); +int treeobj_get_symlink (const json_t *obj, + const char **ns, + const char **target); /* get decoded val data. * If len == 0, data will be NULL. diff --git a/src/modules/kvs/kvstxn.c b/src/modules/kvs/kvstxn.c index d344fd6c73e4..0b3606848407 100644 --- a/src/modules/kvs/kvstxn.c +++ b/src/modules/kvs/kvstxn.c @@ -638,20 +638,23 @@ static int kvstxn_link_dirent (kvstxn_t *kt, int current_epoch, } json_decref (subdir); } else if (treeobj_is_symlink (dir_entry)) { - json_t *symlink = treeobj_get_data (dir_entry); - const char *symlinkstr; + const char *ns = NULL; + const char *target = NULL; char *nkey = NULL; - if (!symlink) { + if (treeobj_get_symlink (dir_entry, &ns, &target) < 0) { saved_errno = EINVAL; goto done; } + assert (target); - assert (json_is_string (symlink)); - - symlinkstr = json_string_value (symlink); + /* can't cross into a new namespace */ + if (ns && strcmp (ns, kt->ktm->ns_name)) { + saved_errno = EINVAL; + goto done; + } - if (asprintf (&nkey, "%s.%s", symlinkstr, next) < 0) { + if (asprintf (&nkey, "%s.%s", target, next) < 0) { saved_errno = ENOMEM; goto done; } diff --git a/src/modules/kvs/lookup.c b/src/modules/kvs/lookup.c index ac60b86021d2..3795cfe3ae28 100644 --- a/src/modules/kvs/lookup.c +++ b/src/modules/kvs/lookup.c @@ -216,6 +216,39 @@ static walk_level_t *walk_levels_push (lookup_t *lh, return wl; } +static lookup_process_t symlink_check_namespace (lookup_t *lh, + const char *ns, + struct kvsroot **rootp) +{ + struct kvsroot *root = NULL; + lookup_process_t ret = LOOKUP_PROCESS_ERROR; + + root = kvsroot_mgr_lookup_root (lh->krm, ns); + + if (!root) { + free (lh->missing_namespace); + if (!(lh->missing_namespace = strdup (ns))) { + lh->errnum = ENOMEM; + goto done; + } + ret = LOOKUP_PROCESS_LOAD_MISSING_NAMESPACE; + goto done; + } + + if (kvsroot_check_user (lh->krm, + root, + lh->rolemask, + lh->userid) < 0) { + lh->errnum = EPERM; + goto done; + } + + (*rootp) = root; + ret = LOOKUP_PROCESS_FINISHED; +done: + return ret; +} + /* If recursing, wlp will be set to new walk level pointer */ static lookup_process_t walk_symlink (lookup_t *lh, @@ -225,13 +258,16 @@ static lookup_process_t walk_symlink (lookup_t *lh, walk_level_t **wlp) { lookup_process_t ret = LOOKUP_PROCESS_ERROR; + struct kvsroot *root = NULL; walk_level_t *wltmp; - const char *linkstr; + const char *ns = NULL; + const char *target = NULL; - if (!(linkstr = treeobj_get_symlink (dirent_tmp))) { + if (treeobj_get_symlink (dirent_tmp, &ns, &target) < 0) { lh->errnum = errno; goto cleanup; } + assert (target); /* Follow link if in middle of path or if end of path, * flags say we can follow it @@ -239,27 +275,48 @@ static lookup_process_t walk_symlink (lookup_t *lh, if (!last_pathcomp (wl->pathcomps, current_pathcomp) || (!(lh->flags & FLUX_KVS_READLINK) && !(lh->flags & FLUX_KVS_TREEOBJ))) { - if (wl->depth == SYMLINK_CYCLE_LIMIT) { lh->errnum = ELOOP; goto cleanup; } - /* Set wl->dirent, now that we've resolved any - * namespaces in the linkstr. + if (ns) { + lookup_process_t nsret; + nsret = symlink_check_namespace (lh, + ns, + &root); + if (nsret != LOOKUP_PROCESS_FINISHED) { + ret = nsret; + goto cleanup; + } + } + + /* Set wl->dirent, now that we've resolved any potential + * namespace in the target. */ wl->dirent = dirent_tmp; - /* if symlink is root, no need to recurse, just get + /* if symlink target is root, no need to recurse, just get * root_dirent and continue on. */ - if (!strcmp (linkstr, ".")) - wl->dirent = wl->root_dirent; + if (!strcmp (target, ".")) { + if (root) { + free (wl->tmp_dirent); + wl->tmp_dirent = treeobj_create_dirref (root->ref); + if (!wl->tmp_dirent) { + lh->errnum = errno; + goto cleanup; + } + wl->dirent = wl->tmp_dirent; + } + else + wl->dirent = wl->root_dirent; + } else { /* "recursively" determine link dirent */ if (!(wltmp = walk_levels_push (lh, - wl->root_ref, - linkstr, + root ? root->ref : wl->root_ref, + target, wl->depth + 1))) { lh->errnum = errno; goto cleanup; diff --git a/src/modules/kvs/test/kvstxn.c b/src/modules/kvs/test/kvstxn.c index b42cdbb71117..89c270000aae 100644 --- a/src/modules/kvs/test/kvstxn.c +++ b/src/modules/kvs/test/kvstxn.c @@ -172,9 +172,9 @@ void _treeobj_insert_entry_val (json_t *obj, const char *name, * created symlink can be properly dereferenced */ void _treeobj_insert_entry_symlink (json_t *obj, const char *name, - const char *target) + const char *ns, const char *target) { - json_t *symlink = treeobj_create_symlink (target); + json_t *symlink = treeobj_create_symlink (ns, target); treeobj_insert_entry (obj, name, symlink); json_decref (symlink); } @@ -2015,7 +2015,7 @@ void kvstxn_process_invalid_hash (void) json_decref (root); } -void kvstxn_process_follow_link (void) +void kvstxn_process_follow_link_no_namespace (void) { struct cache *cache; kvsroot_mgr_t *krm; @@ -2053,7 +2053,7 @@ void kvstxn_process_follow_link (void) root = treeobj_create_dir (); _treeobj_insert_entry_dirref (root, "dir", dir_ref); - _treeobj_insert_entry_symlink (root, "symlink", "dir"); + _treeobj_insert_entry_symlink (root, "symlink", NULL, "dir"); ok (treeobj_hash ("sha1", root, root_ref, sizeof (root_ref)) == 0, "treeobj_hash worked"); @@ -2097,6 +2097,105 @@ void kvstxn_process_follow_link (void) json_decref (root); } +void kvstxn_process_follow_link_namespace (void) +{ + struct cache *cache; + kvsroot_mgr_t *krm; + kvstxn_mgr_t *ktm; + kvstxn_t *kt; + json_t *root; + char root_ref[BLOBREF_MAX_STRING_SIZE]; + const char *newroot; + + ok ((cache = cache_create ()) != NULL, + "cache_create works"); + ok ((krm = kvsroot_mgr_create (NULL, NULL)) != NULL, + "kvsroot_mgr_create works"); + + /* This root is + * + * root_ref + * "val" : val w/ "42" + * "symlinkNS2A" : symlink to "." in namespace=A + * "symlinkNS2B" : symlink to "." in namespace=B + */ + + root = treeobj_create_dir (); + _treeobj_insert_entry_val (root, "val", "42", 2); + _treeobj_insert_entry_symlink (root, "symlinkNS2A", "A", "."); + _treeobj_insert_entry_symlink (root, "symlinkNS2B", "B", "."); + + ok (treeobj_hash ("sha1", root, root_ref, sizeof (root_ref)) == 0, + "treeobj_hash worked"); + + (void)cache_insert (cache, create_cache_entry_treeobj (root_ref, root)); + + setup_kvsroot (krm, "A", cache, root_ref); + + /* First test, follow namespace in symlink within same namespace */ + + ok ((ktm = kvstxn_mgr_create (cache, + "A", + "sha1", + NULL, + &test_global)) != NULL, + "kvstxn_mgr_create works"); + + create_ready_kvstxn (ktm, "transaction1", "symlinkNS2A.val", "100", 0, 0); + + ok ((kt = kvstxn_mgr_get_ready_transaction (ktm)) != NULL, + "kvstxn_mgr_get_ready_transaction returns ready kvstxn"); + + ok (kvstxn_process (kt, 1, root_ref) == KVSTXN_PROCESS_DIRTY_CACHE_ENTRIES, + "kvstxn_process returns KVSTXN_PROCESS_DIRTY_CACHE_ENTRIES"); + + ok (kvstxn_iter_dirty_cache_entries (kt, cache_noop_cb, NULL) == 0, + "kvstxn_iter_dirty_cache_entries works for dirty cache entries"); + + ok (kvstxn_process (kt, 1, root_ref) == KVSTXN_PROCESS_FINISHED, + "kvstxn_process returns KVSTXN_PROCESS_FINISHED"); + + ok ((newroot = kvstxn_get_newroot_ref (kt)) != NULL, + "kvstxn_get_newroot_ref returns != NULL when processing complete"); + + verify_keys_and_ops_standard (kt); + + verify_value (cache, krm, "A", newroot, "val", "100"); + + memcpy (root_ref, newroot, sizeof (root_ref)); + + kvstxn_mgr_remove_transaction (ktm, kt, false); + + kvstxn_mgr_destroy (ktm); + + /* Second test, namespace crossing in symlink results in error */ + + ok ((ktm = kvstxn_mgr_create (cache, + "A", + "sha1", + NULL, + &test_global)) != NULL, + "kvstxn_mgr_create works"); + + create_ready_kvstxn (ktm, "transaction1", "symlinkNS2B.val", "200", 0, 0); + + ok ((kt = kvstxn_mgr_get_ready_transaction (ktm)) != NULL, + "kvstxn_mgr_get_ready_transaction returns ready kvstxn"); + + ok (kvstxn_process (kt, 1, root_ref) == KVSTXN_PROCESS_ERROR, + "kvstxn_process returns KVSTXN_PROCESS_ERROR"); + + ok (kvstxn_get_errnum (kt) == EINVAL, + "kvstxn_get_errnum return EINVAL"); + + kvstxn_mgr_remove_transaction (ktm, kt, false); + + kvstxn_mgr_destroy (ktm); + kvsroot_mgr_destroy (krm); + cache_destroy (cache); + json_decref (root); +} + void kvstxn_process_dirval_test (void) { struct cache *cache; @@ -2901,12 +3000,14 @@ void kvstxn_process_append_errors (void) * root_ref * "dir" : empty directory * "symlink" : symlink to "dir" + * "symlinkNS" : symlink to "dir" in namespace=A */ dir = treeobj_create_dir (); root = treeobj_create_dir (); treeobj_insert_entry (root, "dir", dir); - _treeobj_insert_entry_symlink (root, "symlink", "dir"); + _treeobj_insert_entry_symlink (root, "symlink", NULL, "dir"); + _treeobj_insert_entry_symlink (root, "symlinkNS", "A", "dir"); ok (treeobj_hash ("sha1", root, root_ref, sizeof (root_ref)) == 0, "treeobj_hash worked"); @@ -2954,6 +3055,23 @@ void kvstxn_process_append_errors (void) kvstxn_mgr_remove_transaction (ktm, kt, false); + /* + * append to a symlinkNS, should get EOPNOTSUPP + */ + + create_ready_kvstxn (ktm, "transaction3", "symlinkNS", "3", FLUX_KVS_APPEND, 0); + + ok ((kt = kvstxn_mgr_get_ready_transaction (ktm)) != NULL, + "kvstxn_mgr_get_ready_transaction returns ready kvstxn"); + + ok (kvstxn_process (kt, 1, root_ref) == KVSTXN_PROCESS_ERROR, + "kvstxn_process returns KVSTXN_PROCESS_ERROR"); + + ok (kvstxn_get_errnum (kt) == EOPNOTSUPP, + "kvstxn_get_errnum return EOPNOTSUPP"); + + kvstxn_mgr_remove_transaction (ktm, kt, false); + kvstxn_mgr_destroy (ktm); kvsroot_mgr_destroy (krm); cache_destroy (cache); @@ -3146,7 +3264,8 @@ int main (int argc, char *argv[]) kvstxn_process_invalid_operation (); kvstxn_process_malformed_operation (); kvstxn_process_invalid_hash (); - kvstxn_process_follow_link (); + kvstxn_process_follow_link_no_namespace (); + kvstxn_process_follow_link_namespace (); kvstxn_process_dirval_test (); kvstxn_process_delete_test (); kvstxn_process_delete_nosubdir_test (); diff --git a/src/modules/kvs/test/lookup.c b/src/modules/kvs/test/lookup.c index 65298da68a3e..84c43a045e77 100644 --- a/src/modules/kvs/test/lookup.c +++ b/src/modules/kvs/test/lookup.c @@ -171,9 +171,9 @@ void _treeobj_insert_entry_val (json_t *obj, const char *name, * created symlink can be properly dereferenced */ void _treeobj_insert_entry_symlink (json_t *obj, const char *name, - const char *target) + const char *ns, const char *target) { - json_t *symlink = treeobj_create_symlink (target); + json_t *symlink = treeobj_create_symlink (ns, target); treeobj_insert_entry (obj, name, symlink); json_decref (symlink); } @@ -694,6 +694,7 @@ void lookup_basic (void) { * "val" : val to "foo" * "dir" : dir w/ "val" : val to "bar" * "symlink" : symlink to "baz" + * "symlinkNS" : symlink to "boz" in namespace=primary * * root_ref * "dirref" : dirref to dirref_ref @@ -719,7 +720,8 @@ void lookup_basic (void) { _treeobj_insert_entry_valref (dirref, "valref_with_dirref", dirref_test_ref); _treeobj_insert_entry_val (dirref, "val", "foo", 3); treeobj_insert_entry (dirref, "dir", dir); - _treeobj_insert_entry_symlink (dirref, "symlink", "baz"); + _treeobj_insert_entry_symlink (dirref, "symlink", NULL, "baz"); + _treeobj_insert_entry_symlink (dirref, "symlinkNS", KVS_PRIMARY_NAMESPACE, "boz"); valref_multi = treeobj_create_valref (valref_ref); treeobj_append_blobref (valref_multi, valref2_ref); @@ -874,10 +876,27 @@ void lookup_basic (void) { FLUX_KVS_READLINK, NULL)) != NULL, "lookup_create on path dirref.symlink"); - test = treeobj_create_symlink ("baz"); + test = treeobj_create_symlink (NULL, "baz"); check_value (lh, test, "lookup dirref.symlink"); json_decref (test); + /* lookup symlinkNS */ + ok ((lh = lookup_create (cache, + krm, + 1, + KVS_PRIMARY_NAMESPACE, + NULL, + 0, + "dirref.symlinkNS", + FLUX_ROLE_OWNER, + 0, + FLUX_KVS_READLINK, + NULL)) != NULL, + "lookup_create on path dirref.symlinkNS"); + test = treeobj_create_symlink (KVS_PRIMARY_NAMESPACE, "boz"); + check_value (lh, test, "lookup dirref.symlinkNS"); + json_decref (test); + /* lookup dirref treeobj */ ok ((lh = lookup_create (cache, krm, @@ -957,10 +976,27 @@ void lookup_basic (void) { FLUX_KVS_TREEOBJ, NULL)) != NULL, "lookup_create on path dirref.symlink (treeobj)"); - test = treeobj_create_symlink ("baz"); + test = treeobj_create_symlink (NULL, "baz"); check_value (lh, test, "lookup dirref.symlink treeobj"); json_decref (test); + /* lookup symlinkNS treeobj */ + ok ((lh = lookup_create (cache, + krm, + 1, + KVS_PRIMARY_NAMESPACE, + NULL, + 0, + "dirref.symlinkNS", + FLUX_ROLE_OWNER, + 0, + FLUX_KVS_TREEOBJ, + NULL)) != NULL, + "lookup_create on path dirref.symlinkNS (treeobj)"); + test = treeobj_create_symlink (KVS_PRIMARY_NAMESPACE, "boz"); + check_value (lh, test, "lookup dirref.symlinkNS treeobj"); + json_decref (test); + cache_destroy (cache); kvsroot_mgr_destroy (krm); json_decref (dirref_test); @@ -1001,6 +1037,11 @@ void lookup_errors (void) { * "symlink" : symlink to "symlinkstr" * "symlink1" : symlink to "symlink2" * "symlink2" : symlink to "symlink1" + * "symlinkNS" : symlink to "symlinkNSstr" in namespace=primary + * "symlinkNS1" : symlink to "symlinkNS2" in namespace=primary + * "symlinkNS2" : symlink to "symlinkNS1" in namespace=primary + * "ns2symlink" : symlink to "symlink2ns" in namespace=primary + * "symlink2ns" : symlink to "ns2symlink" * "val" : val to "foo" * "valref" : valref to valref_ref * "dirref" : dirref to dirref_ref @@ -1021,9 +1062,14 @@ void lookup_errors (void) { _treeobj_insert_entry_val (dir, "val", "baz", 3); root = treeobj_create_dir (); - _treeobj_insert_entry_symlink (root, "symlink", "symlinkstr"); - _treeobj_insert_entry_symlink (root, "symlink1", "symlink2"); - _treeobj_insert_entry_symlink (root, "symlink2", "symlink1"); + _treeobj_insert_entry_symlink (root, "symlink", NULL, "symlinkstr"); + _treeobj_insert_entry_symlink (root, "symlink1", NULL, "symlink2"); + _treeobj_insert_entry_symlink (root, "symlink2", NULL, "symlink1"); + _treeobj_insert_entry_symlink (root, "symlinkNS", KVS_PRIMARY_NAMESPACE, "symlinkNSstr"); + _treeobj_insert_entry_symlink (root, "symlinkNS1", KVS_PRIMARY_NAMESPACE, "symlinkNS2"); + _treeobj_insert_entry_symlink (root, "symlinkNS2", KVS_PRIMARY_NAMESPACE, "symlinkNS1"); + _treeobj_insert_entry_symlink (root, "symlinkNS2symlink", KVS_PRIMARY_NAMESPACE, "symlink2symlinkNS"); + _treeobj_insert_entry_symlink (root, "symlink2symlinkNS", NULL, "symlinkNS2symlink"); _treeobj_insert_entry_val (root, "val", "foo", 3); _treeobj_insert_entry_valref (root, "valref", valref_ref); _treeobj_insert_entry_dirref (root, "dirref", dirref_ref); @@ -1118,6 +1164,36 @@ void lookup_errors (void) { "lookup_create on link loop"); check_error (lh, ELOOP, "lookup infinite links"); + /* Lookup path w/ infinite symlinkNS loop, should get ELOOP */ + ok ((lh = lookup_create (cache, + krm, + 1, + KVS_PRIMARY_NAMESPACE, + NULL, + 0, + "symlinkNS1", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create on symlinkNS loop"); + check_error (lh, ELOOP, "lookup infinite symlinkNS loop"); + + /* Lookup path w/ infinite symlink w/ & w/o namespace loop, should get ELOOP */ + ok ((lh = lookup_create (cache, + krm, + 1, + KVS_PRIMARY_NAMESPACE, + NULL, + 0, + "symlinkNS2symlink", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create on symlink (w/ & w/o namespace) loop"); + check_error (lh, ELOOP, "lookup infinite symlink (w/ & w/o namespace) loop"); + /* Lookup a dirref, but expecting a link, should get EINVAL. */ ok ((lh = lookup_create (cache, krm, @@ -1253,6 +1329,21 @@ void lookup_errors (void) { "lookup_create on symlink"); check_error (lh, ENOTDIR, "lookup symlink, expecting dir"); + /* Lookup a symlinkNS, but expecting a dir, should get ENOTDIR. */ + ok ((lh = lookup_create (cache, + krm, + 1, + KVS_PRIMARY_NAMESPACE, + NULL, + 0, + "symlinkNS", + FLUX_ROLE_OWNER, + 0, + FLUX_KVS_READLINK | FLUX_KVS_READDIR, + NULL)) != NULL, + "lookup_create on symlinkNS"); + check_error (lh, ENOTDIR, "lookup symlinkNS, expecting dir"); + /* Lookup a dirref that doesn't point to a dir, should get ENOTRECOVERABLE. */ ok ((lh = lookup_create (cache, krm, @@ -1585,16 +1676,16 @@ void lookup_links (void) { _treeobj_insert_entry_valref (dirref2, "valref", valref_ref); treeobj_insert_entry (dirref2, "dir", dir); _treeobj_insert_entry_dirref (dirref2, "dirref", dirref3_ref); - _treeobj_insert_entry_symlink (dirref2, "symlink", "dirref2.val"); + _treeobj_insert_entry_symlink (dirref2, "symlink", NULL, "dirref2.val"); treeobj_hash ("sha1", dirref2, dirref2_ref, sizeof (dirref2_ref)); (void)cache_insert (cache, create_cache_entry_treeobj (dirref2_ref, dirref2)); dirref1 = treeobj_create_dir (); - _treeobj_insert_entry_symlink (dirref1, "link2dirref", "dirref2"); - _treeobj_insert_entry_symlink (dirref1, "link2val", "dirref2.val"); - _treeobj_insert_entry_symlink (dirref1, "link2valref", "dirref2.valref"); - _treeobj_insert_entry_symlink (dirref1, "link2dir", "dirref2.dir"); - _treeobj_insert_entry_symlink (dirref1, "link2symlink", "dirref2.symlink"); + _treeobj_insert_entry_symlink (dirref1, "link2dirref", NULL, "dirref2"); + _treeobj_insert_entry_symlink (dirref1, "link2val", NULL, "dirref2.val"); + _treeobj_insert_entry_symlink (dirref1, "link2valref", NULL, "dirref2.valref"); + _treeobj_insert_entry_symlink (dirref1, "link2dir", NULL, "dirref2.dir"); + _treeobj_insert_entry_symlink (dirref1, "link2symlink", NULL, "dirref2.symlink"); treeobj_hash ("sha1", dirref1, dirref1_ref, sizeof (dirref1_ref)); (void)cache_insert (cache, create_cache_entry_treeobj (dirref1_ref, dirref1)); @@ -1700,7 +1791,7 @@ void lookup_links (void) { FLUX_KVS_READLINK, NULL)) != NULL, "lookup_create link to symlink"); - test = treeobj_create_symlink ("dirref2.val"); + test = treeobj_create_symlink (NULL, "dirref2.val"); check_value (lh, test, "dirref1.link2dirref.symlink"); json_decref (test); @@ -1781,7 +1872,7 @@ void lookup_links (void) { FLUX_KVS_READLINK, NULL)) != NULL, "lookup_create link to symlink (last part path)"); - test = treeobj_create_symlink ("dirref2.symlink"); + test = treeobj_create_symlink (NULL, "dirref2.symlink"); check_value (lh, test, "dirref1.link2symlink"); json_decref (test); @@ -1919,13 +2010,13 @@ void lookup_root_symlink (void) { (void)cache_insert (cache, create_cache_entry_raw (valref_ref, "abcd", 4)); dirref = treeobj_create_dir (); - _treeobj_insert_entry_symlink (dirref, "symlinkroot", "."); + _treeobj_insert_entry_symlink (dirref, "symlinkroot", NULL, "."); treeobj_hash ("sha1", dirref, dirref_ref, sizeof (dirref_ref)); (void)cache_insert (cache, create_cache_entry_treeobj (dirref_ref, dirref)); root = treeobj_create_dir (); _treeobj_insert_entry_val (root, "val", "foo", 3); - _treeobj_insert_entry_symlink (root, "symlinkroot", "."); + _treeobj_insert_entry_symlink (root, "symlinkroot", NULL, "."); _treeobj_insert_entry_dirref (root, "dirref", dirref_ref); treeobj_hash ("sha1", root, root_ref, sizeof (root_ref)); (void)cache_insert (cache, create_cache_entry_treeobj (root_ref, root)); @@ -1991,7 +2082,7 @@ void lookup_root_symlink (void) { FLUX_KVS_TREEOBJ, NULL)) != NULL, "lookup_create on symlinkroot w/ flag = FLUX_KVS_TREEOBJ, works"); - test = treeobj_create_symlink ("."); + test = treeobj_create_symlink (NULL, "."); check_value (lh, test, "symlinkroot w/ FLUX_KVS_TREEOBJ"); json_decref (test); @@ -2047,6 +2138,308 @@ void lookup_root_symlink (void) { json_decref (root); } +/* lookup symlinkNS tests */ +void lookup_symlinkNS (void) { + json_t *rootA; + json_t *rootB; + json_t *test; + struct cache *cache; + kvsroot_mgr_t *krm; + lookup_t *lh; + char root_refA[BLOBREF_MAX_STRING_SIZE]; + char root_refB[BLOBREF_MAX_STRING_SIZE]; + + ok ((cache = cache_create ()) != NULL, + "cache_create works"); + ok ((krm = kvsroot_mgr_create (NULL, NULL)) != NULL, + "kvsroot_mgr_create works"); + + /* This cache is + * + * root-refA + * "val" : val to "1" + * "symlinkNS2A-invalid" : symlinkNS to an invalid key in namespace=A + * "symlinkNS2B-invalid" : symlinkNS to an invalid key in namespace=B + * "symlinkNS2A" : symlinkNS to "." in namespace=A + * "symlinkNS2B" : symlinkNS to "." in namespace=B + * "symlinkNS2A-val" : symlinkNS to "val" in namespace=A + * "symlinkNS2B-val" : symlinkNS to "val" in namespace=B + * + * root-refB + * "val" : val to "2" + */ + + rootA = treeobj_create_dir (); + _treeobj_insert_entry_val (rootA, "val", "1", 1); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2A-invalid", "A", "foobar"); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2B-invalid", "A", "foobar"); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2A", "A", "."); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2B", "B", "."); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2A-val", "A", "val"); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2B-val", "B", "val"); + treeobj_hash ("sha1", rootA, root_refA, sizeof (root_refA)); + + (void)cache_insert (cache, create_cache_entry_treeobj (root_refA, rootA)); + + rootB = treeobj_create_dir (); + _treeobj_insert_entry_val (rootB, "val", "2", 1); + treeobj_hash ("sha1", rootB, root_refB, sizeof (root_refB)); + + (void)cache_insert (cache, create_cache_entry_treeobj (root_refB, rootB)); + + setup_kvsroot (krm, "A", cache, root_refA, 0); + setup_kvsroot (krm, "B", cache, root_refB, 0); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2A-invalid", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create symlinkNS2A-invalid on namespace A"); + check_value (lh, NULL, "symlinkNS2A-invalid on namespace A"); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2B-invalid", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create symlinkNS2B-invalid on namespace A"); + check_value (lh, NULL, "symlinkNS2B-invalid on namespace A"); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2A.val", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create symlinkNS2A.val on namespace A"); + test = treeobj_create_val ("1", 1); + check_value (lh, test, "symlinkNS2A.val on namespace A"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2B.val", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create symlinkNS2B.val on namespace A"); + test = treeobj_create_val ("2", 1); + check_value (lh, test, "symlinkNS2B.val on namespace A"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2A-val", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create symlinkNS2A-val on namespace A"); + test = treeobj_create_val ("1", 1); + check_value (lh, test, "symlinkNS2A-val on namespace A"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2B-val", + FLUX_ROLE_OWNER, + 0, + 0, + NULL)) != NULL, + "lookup_create symlinkNS2B-val on namespace A"); + test = treeobj_create_val ("2", 1); + check_value (lh, test, "symlinkNS2B-val on namespace A"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2A", + FLUX_ROLE_OWNER, + 0, + FLUX_KVS_READDIR, + NULL)) != NULL, + "lookup_create symlinkNS2A on namespace A, readdir"); + check_value (lh, rootA, "symlinkNS2A on namespace A, readdir"); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2B", + FLUX_ROLE_OWNER, + 0, + FLUX_KVS_READDIR, + NULL)) != NULL, + "lookup_create symlinkNS2B on namespace A, readdir"); + check_value (lh, rootB, "symlinkNS2B on namespace A, readdir"); + + cache_destroy (cache); + kvsroot_mgr_destroy (krm); + json_decref (rootA); + json_decref (rootB); +} + +void lookup_symlinkNS_security (void) { + json_t *rootA; + json_t *rootB; + json_t *rootC; + json_t *test; + struct cache *cache; + kvsroot_mgr_t *krm; + lookup_t *lh; + char root_refA[BLOBREF_MAX_STRING_SIZE]; + char root_refB[BLOBREF_MAX_STRING_SIZE]; + char root_refC[BLOBREF_MAX_STRING_SIZE]; + + ok ((cache = cache_create ()) != NULL, + "cache_create works"); + ok ((krm = kvsroot_mgr_create (NULL, NULL)) != NULL, + "kvsroot_mgr_create works"); + + /* This cache is + * + * root-refA + * "val" : val to "1" + * "symlinkNS2B" : symlinkNS to "." in namespace=B + * "symlinkNS2C" : symlinkNS to "." in namespace=C + * + * root-refB + * "val" : val to "2" + * + * root-refC + * "val" : val to "3" + */ + + rootA = treeobj_create_dir (); + _treeobj_insert_entry_val (rootA, "val", "1", 1); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2B", "B", "."); + _treeobj_insert_entry_symlink (rootA, "symlinkNS2C", "C", "."); + treeobj_hash ("sha1", rootA, root_refA, sizeof (root_refA)); + + (void)cache_insert (cache, create_cache_entry_treeobj (root_refA, rootA)); + + rootB = treeobj_create_dir (); + _treeobj_insert_entry_val (rootB, "val", "2", 1); + treeobj_hash ("sha1", rootB, root_refB, sizeof (root_refB)); + + (void)cache_insert (cache, create_cache_entry_treeobj (root_refB, rootB)); + + rootC = treeobj_create_dir (); + _treeobj_insert_entry_val (rootC, "val", "3", 1); + treeobj_hash ("sha1", rootC, root_refC, sizeof (root_refC)); + + (void)cache_insert (cache, create_cache_entry_treeobj (root_refC, rootC)); + + setup_kvsroot (krm, "A", cache, root_refA, 1000); + setup_kvsroot (krm, "B", cache, root_refB, 1000); + setup_kvsroot (krm, "C", cache, root_refC, 2000); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2B.val", + FLUX_ROLE_OWNER, + 1000, + 0, + NULL)) != NULL, + "lookup_create on symlinkNS2B.val with rolemask owner"); + test = treeobj_create_val ("2", 1); + check_value (lh, test, "lookup symlinkNS2B.val with rolemask owner"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2C.val", + FLUX_ROLE_OWNER, + 1000, + 0, + NULL)) != NULL, + "lookup_create on symlinkNS2C.val with rolemask owner"); + test = treeobj_create_val ("3", 1); + check_value (lh, test, "lookup symlinkNS2C.val with rolemask owner"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2B.val", + FLUX_ROLE_USER, + 1000, + 0, + NULL)) != NULL, + "lookup_create on symlinkNS2B.val with rolemask user and valid owner"); + test = treeobj_create_val ("2", 1); + check_value (lh, test, "lookup symlinkNS2B.val with rolemask user and valid owner"); + json_decref (test); + + ok ((lh = lookup_create (cache, + krm, + 1, + "A", + NULL, + 0, + "symlinkNS2C.val", + FLUX_ROLE_USER, + 1000, + 0, + NULL)) != NULL, + "lookup_create on symlinkNS2C.val with rolemask user and invalid owner"); + check_error (lh, EPERM, "lookup_create on symlinkNS2C.val with rolemask user and invalid owner"); + + cache_destroy (cache); + kvsroot_mgr_destroy (krm); + json_decref (rootA); + json_decref (rootB); + json_decref (rootC); +} + /* lookup stall namespace tests */ void lookup_stall_namespace (void) { json_t *root1; @@ -2370,7 +2763,7 @@ void lookup_stall_ref (void) { root = treeobj_create_dir (); _treeobj_insert_entry_dirref (root, "dirref1", dirref1_ref); _treeobj_insert_entry_dirref (root, "dirref2", dirref2_ref); - _treeobj_insert_entry_symlink (root, "symlink", "dirref2"); + _treeobj_insert_entry_symlink (root, "symlink", NULL, "dirref2"); treeobj_hash ("sha1", root, root_ref, sizeof (root_ref)); setup_kvsroot (krm, KVS_PRIMARY_NAMESPACE, cache, root_ref, 0); @@ -2990,6 +3383,8 @@ int main (int argc, char *argv[]) lookup_links (); lookup_alt_root (); lookup_root_symlink (); + lookup_symlinkNS (); + lookup_symlinkNS_security (); lookup_stall_namespace (); lookup_stall_ref_root (); lookup_stall_ref (); diff --git a/t/t1000-kvs.t b/t/t1000-kvs.t index 81da247c6b2c..ef08480eca1a 100755 --- a/t/t1000-kvs.t +++ b/t/t1000-kvs.t @@ -466,10 +466,12 @@ test_expect_success 'kvs: treeobj of all types handled by get --treeobj' ' flux kvs put $DIR.valref=$(seq -s 1 100) && flux kvs mkdir $DIR.dirref && flux kvs link foo $DIR.symlink && + flux kvs link --target-namespace=A bar $DIR.symlinkNS && flux kvs get --treeobj $DIR.val | grep -q val && flux kvs get --treeobj $DIR.valref | grep -q valref && flux kvs get --treeobj $DIR.dirref | grep -q dirref && - flux kvs get --treeobj $DIR.symlink | grep -q symlink + flux kvs get --treeobj $DIR.symlink | grep -q symlink && + flux kvs get --treeobj $DIR.symlinkNS | grep -q symlink ' test_expect_success 'kvs: get --treeobj: returns value ref for large value' ' flux kvs unlink -Rf $DIR && @@ -599,7 +601,10 @@ EOF # # ls tests # + test_expect_success 'kvs: ls -1F works' ' + flux kvs unlink -Rf $DIR && + flux kvs mkdir $DIR && flux kvs ls -1F >output && cat >expected <<-EOF && test. @@ -607,6 +612,8 @@ test_expect_success 'kvs: ls -1F works' ' test_cmp expected output ' test_expect_success 'kvs: ls -1F . works' ' + flux kvs unlink -Rf $DIR && + flux kvs mkdir $DIR && flux kvs ls -1F . >output && cat >expected <<-EOF && test. @@ -618,11 +625,13 @@ test_expect_success 'kvs: ls -1F DIR works' ' flux kvs put --json $DIR.a=69 && flux kvs mkdir $DIR.b && flux kvs link b $DIR.c && + flux kvs link --target-namespace=foo c $DIR.d && flux kvs ls -1F $DIR >output && cat >expected <<-EOF && a b. c@ + d@ EOF test_cmp expected output ' @@ -632,19 +641,23 @@ test_expect_success 'kvs: ls -1F DIR. works' ' a b. c@ + d@ EOF test_cmp expected output ' -test_expect_success 'kvs: ls -1Fd DIR.a DIR.b DIR.c works' ' +test_expect_success 'kvs: ls -1Fd DIR.a DIR.b DIR.c DIR.d works' ' flux kvs unlink -Rf $DIR && flux kvs put --json $DIR.a=69 && flux kvs mkdir $DIR.b && flux kvs link b $DIR.c && - flux kvs ls -1Fd $DIR.a $DIR.b $DIR.c >output && + flux kvs link --target-namespace=foo c $DIR.d && + flux kvs ls -1Fd $DIR.a $DIR.b $DIR.c $DIR.d >output-1 && + flux kvs ls -1Fd $DIR.a $DIR.b $DIR.c $DIR.d >output && cat >expected <<-EOF && $DIR.a $DIR.b. $DIR.c@ + $DIR.d@ EOF test_cmp expected output ' @@ -653,6 +666,7 @@ test_expect_success 'kvs: ls -1RF shows directory titles' ' flux kvs put --json $DIR.a=69 && flux kvs put --json $DIR.b.d=42 && flux kvs link b $DIR.c && + flux kvs ls -1RF $DIR | grep : >output-2 && flux kvs ls -1RF $DIR | grep : | wc -l >output && cat >expected <<-EOF && 2 @@ -868,9 +882,98 @@ test_expect_success 'kvs: link: path resolution with intermediate link and nonex test_must_fail flux kvs get --json $DIR.Z.Y ' +# +# link/readlink tests - across namespace +# + +test_expect_success 'kvs: namespace create setup' ' + flux kvs namespace-create TESTSYMLINKNS +' +test_expect_success 'kvs: symlink w/ Namespace works' ' + TARGET=$DIR.target && + flux kvs --namespace=TESTSYMLINKNS put --json $TARGET=\"foo\" && + flux kvs link --target-namespace=TESTSYMLINKNS $TARGET $DIR.symlinkNS && + OUTPUT=$(flux kvs get --json $DIR.symlinkNS) && + test "$OUTPUT" = "foo" +' +test_expect_success 'kvs: symlink w/ Namespace fails on bad namespace' ' + TARGET=$DIR.target && + flux kvs put --json $TARGET=\"foo\" && + flux kvs link --target-namespace=TESTSYMLINKNS-FAKE $TARGET $DIR.symlinkNS && + ! flux kvs get --json $DIR.symlinkNS +' +test_expect_success 'kvs: readlink on symlink w/ Namespace works' ' + TARGET=$DIR.target && + flux kvs put --json $TARGET=\"foo\" && + flux kvs link --target-namespace=TESTSYMLINKNS $TARGET $DIR.symlinkNS && + OUTPUT=$(flux kvs readlink $DIR.symlinkNS) && + test "$OUTPUT" = "TESTSYMLINKNS::$TARGET" +' +test_expect_success 'kvs: readlink works with nslnks (multiple inputs)' ' + TARGET1=$DIR.target1 && + TARGET2=$DIR.target2 && + flux kvs --namespace=TESTSYMLINKNS put --json $TARGET1=\"foo1\" && + flux kvs --namespace=TESTSYMLINKNS put --json $TARGET2=\"foo2\" && + flux kvs link --target-namespace=TESTSYMLINKNS $TARGET1 $DIR.symlinkNS1 && + flux kvs link --target-namespace=TESTSYMLINKNS $TARGET2 $DIR.symlinkNS2 && + flux kvs readlink $DIR.symlinkNS1 $DIR.symlinkNS2 >output && + cat >expected <