A comma-separated target list can be miscounted, causing cxl create-region
to reject a valid multi-target request when --ways is omitted. The
generated filter can also acquire a trailing comma.

This happens because the separator is re-evaluated after tokenization has
already modified the input.

Choose the separator before tokenizing and use the target count when
emitting the flattened list.

Fixes: 3d6cd829ec08 ("cxl/region: Use cxl_filter_walk() to gather create-region 
targets")
Assisted-by: LLM
Signed-off-by: Alison Schofield <[email protected]>
---
 cxl/region.c              | 21 +++++++-----
 test/cxl-create-region.sh | 72 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+), 9 deletions(-)

diff --git a/cxl/region.c b/cxl/region.c
index 85d4d9bb54f2..43c228f2f999 100644
--- a/cxl/region.c
+++ b/cxl/region.c
@@ -124,24 +124,25 @@ static const struct option destroy_options[] = {
 static const char *to_csv(int *count, const char **strings)
 {
        ssize_t len = 0, cursor = 0;
+       int i, new_count = 0, emitted = 0;
        char *csv, *list, *save;
-       int i, new_count = 0;
-       const char *arg;
+       const char *arg, *sep;
 
        if (!*count)
                return NULL;
 
        for (i = 0; i < *count; i++) {
                /*
-                * An entry in strings may itself by a space or comma
-                * separated list, so decompose that for the final csv
+                * An entry may itself be space- or comma-separated. strtok_r()
+                * modifies @list, so choose the separator before tokenizing.
                 */
                list = strdup(strings[i]);
                if (!list)
                        return NULL;
 
-               for (arg = strtok_r(list, which_sep(list), &save); arg;
-                    arg = strtok_r(NULL, which_sep(list), &save)) {
+               sep = which_sep(list);
+               for (arg = strtok_r(list, sep, &save); arg;
+                    arg = strtok_r(NULL, sep, &save)) {
                        len += strlen(arg);
                        new_count++;
                }
@@ -161,10 +162,12 @@ static const char *to_csv(int *count, const char 
**strings)
                        return NULL;
                }
 
-               for (arg = strtok_r(list, which_sep(list), &save); arg;
-                    arg = strtok_r(NULL, which_sep(list), &save)) {
+               sep = which_sep(list);
+               for (arg = strtok_r(list, sep, &save); arg;
+                    arg = strtok_r(NULL, sep, &save)) {
                        cursor += snprintf(csv + cursor, len - cursor, "%s%s",
-                                          arg, i + 1 < new_count ? "," : "");
+                                          arg,
+                                          ++emitted < new_count ? "," : "");
                        if (cursor >= len) {
                                csv[len - 1] = 0;
                                break;
diff --git a/test/cxl-create-region.sh b/test/cxl-create-region.sh
index 927edf4c17d4..6fc845c7d382 100644
--- a/test/cxl-create-region.sh
+++ b/test/cxl-create-region.sh
@@ -226,6 +226,76 @@ setup_x3()
        memdevs="$mem0 $mem1 $mem2"
 }
 
+# Build a 4-way target list over an x2 root in interleave position order.
+setup_interleave_shape()
+{
+       x2_decoder=$($CXL list -b cxl_test -D -d root |
+                    jq -r ".[] |
+                    select(.pmem_capable == true) |
+                    select(.nr_targets == 2) |
+                    .decoder")
+
+       if [[ ! $x2_decoder ]]; then
+               echo "no pmem capable x2 root decoder found"
+               return 1
+       fi
+
+       pos0=$(find_memdev "$x2_decoder" 0 0)
+       pos1=$(find_memdev "$x2_decoder" 1 0)
+       pos2=$(find_memdev "$x2_decoder" 0 1)
+       pos3=$(find_memdev "$x2_decoder" 1 1)
+
+       if [[ ! $pos0 || ! $pos1 || ! $pos2 || ! $pos3 ]]; then
+               echo "$x2_decoder: no memdev pair for each interleave position"
+               return 1
+       fi
+}
+
+# A comma-separated target list must preserve its count and order.
+create_csv_targets()
+{
+       if ! setup_interleave_shape; then
+               echo "skipping comma separated target list"
+               return
+       fi
+
+       if ! json=$($CXL create-region -b cxl_test -d "$x2_decoder" \
+                   -m "$pos0,$pos1,$pos2,$pos3"); then
+               echo "$x2_decoder: refused $pos0,$pos1,$pos2,$pos3"
+               err "$LINENO"
+       fi
+
+       region=$(jq -r ".region" <<< "$json")
+       ways=$(jq -r ".interleave_ways" <<< "$json")
+       if [[ $ways -ne 4 ]]; then
+               echo "$region: interleaves $ways ways, expected 4"
+               err "$LINENO"
+       fi
+
+       targets=$(jq -r "[.mappings | sort_by(.position) | .[].memdev] |
+                 join(\" \")" <<< "$json")
+       if [[ "$targets" != "$pos0 $pos1 $pos2 $pos3" ]]; then
+               echo "$region: targets '$targets', expected '$pos0 $pos1 $pos2 
$pos3'"
+               err "$LINENO"
+       fi
+
+       destroy_regions "$region"
+
+       # Also verify a rejected filter is echoed without a trailing comma.
+       if out=$($CXL create-region -b cxl_test -d "$x2_decoder" \
+                -m nomemdev0,nomemdev1,nomemdev2 2>&1); then
+               echo "create-region accepted names that are not memdevs"
+               err "$LINENO"
+       fi
+
+       if ! grep -Fq -- "filter: nomemdev0,nomemdev1,nomemdev2" <<< "$out" ||
+          grep -Fq -- "nomemdev2," <<< "$out"; then
+               echo "target list echoed back as:"
+               echo "$out"
+               err "$LINENO"
+       fi
+}
+
 # test region creation on devices behind a single-port host-bridge
 create_single
 
@@ -239,6 +309,8 @@ for mem in ${mems[@]}; do
        create_subregions "$mem"
 done
 
+create_csv_targets
+
 # Repeat x1/x2/x3/x4 region creation with XOR interleave arithmetic.
 load_cxl_test interleave_arithmetic=1
 
-- 
2.37.3


Reply via email to