1 /*
2  * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana
3  *                         University Research and Technology
4  *                         Corporation.  All rights reserved.
5  * Copyright (c) 2004-2005 The University of Tennessee and The University
6  *                         of Tennessee Research Foundation.  All rights
7  *                         reserved.
8  * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart,
9  *                         University of Stuttgart.  All rights reserved.
10  * Copyright (c) 2004-2005 The Regents of the University of California.
11  *                         All rights reserved.
12  * Copyright (c) 2013      Cisco Systems, Inc.  All rights reserved.
13  * Copyright (c) 2014-2017 Intel, Inc.  All rights reserved.
14  * Copyright (c) 2015      Research Organization for Information Science
15  *                         and Technology (RIST). All rights reserved.
16  * Copyright (c) 2016      IBM Corporation.  All rights reserved.
17  * $COPYRIGHT$
18  *
19  * Additional copyrights may follow
20  *
21  * $HEADER$
22  */
23 
24 #include "orte_config.h"
25 
26 #include <string.h>
27 #include <ctype.h>
28 
29 #include "orte/constants.h"
30 #include "orte/types.h"
31 
32 #include "orte/util/show_help.h"
33 #include "opal/util/argv.h"
34 #include "opal/util/if.h"
35 #include "opal/util/net.h"
36 
37 #include "orte/mca/ras/base/base.h"
38 #include "orte/mca/plm/plm_types.h"
39 #include "orte/mca/errmgr/errmgr.h"
40 #include "orte/util/proc_info.h"
41 #include "orte/runtime/orte_globals.h"
42 
43 #include "dash_host.h"
44 
45 /* we can only enter this routine if no other allocation
46  * was found, so we only need to know that finding any
47  * relative node syntax should generate an immediate error
48  */
orte_util_add_dash_host_nodes(opal_list_t * nodes,char * hosts,bool allocating)49 int orte_util_add_dash_host_nodes(opal_list_t *nodes,
50                                   char *hosts, bool allocating)
51 {
52     opal_list_item_t *item, *itm;
53     orte_std_cntr_t i, j, k;
54     int rc, nodeidx;
55     char **host_argv=NULL;
56     char **mapped_nodes = NULL, **mini_map, *ndname;
57     orte_node_t *node, *nd;
58     opal_list_t adds;
59     bool found;
60     int slots=0;
61     bool slots_given;
62     char *cptr, *ptr;
63 
64     OPAL_OUTPUT_VERBOSE((1, orte_ras_base_framework.framework_output,
65                          "%s dashhost: parsing args %s",
66                          ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), hosts));
67 
68     OBJ_CONSTRUCT(&adds, opal_list_t);
69     host_argv = opal_argv_split(hosts, ',');
70 
71     /* Accumulate all of the host name mappings */
72     for (j = 0; j < opal_argv_count(host_argv); ++j) {
73         mini_map = opal_argv_split(host_argv[j], ',');
74 
75         if (mapped_nodes == NULL) {
76             mapped_nodes = mini_map;
77         } else {
78             for (k = 0; NULL != mini_map[k]; ++k) {
79                 rc = opal_argv_append_nosize(&mapped_nodes,
80                                              mini_map[k]);
81                 if (OPAL_SUCCESS != rc) {
82                     opal_argv_free(host_argv);
83                     opal_argv_free(mini_map);
84                     goto cleanup;
85                 }
86             }
87             opal_argv_free(mini_map);
88         }
89     }
90     opal_argv_free(host_argv);
91     mini_map = NULL;
92 
93     /* Did we find anything? If not, then do nothing */
94     if (NULL == mapped_nodes) {
95         rc = ORTE_SUCCESS;
96         goto cleanup;
97     }
98 
99     for (i = 0; NULL != mapped_nodes[i]; ++i) {
100         /* if the specified node contains a relative node syntax,
101          * and we are allocating, then ignore it
102          */
103         if ('+' == mapped_nodes[i][0]) {
104             if (!allocating) {
105                 if ('e' == mapped_nodes[i][1] ||
106                     'E' == mapped_nodes[i][1]) {
107                     /* request for empty nodes - do they want
108                      * all of them?
109                      */
110                     if (NULL != (cptr = strchr(mapped_nodes[i], ':'))) {
111                         /* the colon indicates a specific # are requested */
112                         ++cptr;
113                         j = strtoul(cptr, NULL, 10);
114                     } else if ('\0' != mapped_nodes[0][2]) {
115                         j = strtoul(&mapped_nodes[0][2], NULL, 10);
116                     } else {
117                         /* add them all */
118                         j = orte_node_pool->size;
119                     }
120                     for (k=0; 0 < j && k < orte_node_pool->size; k++) {
121                         if (NULL != (node = (orte_node_t*)opal_pointer_array_get_item(orte_node_pool, k))) {
122                             if (0 == node->num_procs) {
123                                 opal_argv_append_nosize(&mini_map, node->name);
124                                 --j;
125                             }
126                         }
127                     }
128                 } else if ('n' == mapped_nodes[i][1] ||
129                            'N' == mapped_nodes[i][1]) {
130                     /* they want a specific relative node #, so
131                      * look it up on global pool
132                      */
133                     if ('\0' == mapped_nodes[i][2]) {
134                         /* they forgot to tell us the # */
135                         orte_show_help("help-dash-host.txt", "dash-host:invalid-relative-node-syntax",
136                                        true, mapped_nodes[i]);
137                         rc = ORTE_ERR_SILENT;
138                         goto cleanup;
139                     }
140                     nodeidx = strtol(&mapped_nodes[i][2], NULL, 10);
141                     if (nodeidx < 0 ||
142                         nodeidx > (int)orte_node_pool->size) {
143                         /* this is an error */
144                         orte_show_help("help-dash-host.txt", "dash-host:relative-node-out-of-bounds",
145                                        true, nodeidx, mapped_nodes[i]);
146                         rc = ORTE_ERR_SILENT;
147                         goto cleanup;
148                     }
149                     /* if the HNP is not allocated, then we need to
150                      * adjust the index as the node pool is offset
151                      * by one
152                      */
153                     if (!orte_hnp_is_allocated) {
154                         nodeidx++;
155                     }
156                     /* see if that location is filled */
157 
158                     if (NULL == (node = (orte_node_t *) opal_pointer_array_get_item(orte_node_pool, nodeidx))) {
159                         /* this is an error */
160                         orte_show_help("help-dash-host.txt", "dash-host:relative-node-not-found",
161                                        true, nodeidx, mapped_nodes[i]);
162                         rc = ORTE_ERR_SILENT;
163                         goto cleanup;
164                     }
165                     /* add this node to the list */
166                     opal_argv_append_nosize(&mini_map, node->name);
167                 } else {
168                     /* invalid relative node syntax */
169                     orte_show_help("help-dash-host.txt", "dash-host:invalid-relative-node-syntax",
170                                    true, mapped_nodes[i]);
171                     rc = ORTE_ERR_SILENT;
172                     goto cleanup;
173                 }
174             }
175         } else {
176             /* just one node was given */
177             opal_argv_append_nosize(&mini_map, mapped_nodes[i]);
178         }
179     }
180     if (NULL == mini_map) {
181         rc = ORTE_SUCCESS;
182         goto cleanup;
183     }
184 
185     /*  go through the names found and
186         add them to the host list. If they're not unique, then
187         bump the slots count for each duplicate */
188     for (i=0; NULL != mini_map[i]; i++) {
189         OPAL_OUTPUT_VERBOSE((1, orte_ras_base_framework.framework_output,
190                              "%s dashhost: working node %s",
191                              ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), mini_map[i]));
192 
193         /* see if the node contains the number of slots */
194         slots_given = false;
195         if (NULL != (cptr = strchr(mini_map[i], ':'))) {
196             *cptr = '\0';
197             ++cptr;
198             if ('*' == *cptr || 0 == strcmp(cptr, "auto")) {
199                 /* auto-detect #slots */
200                 slots = -1;
201                 slots_given = false;
202             } else {
203                 slots = strtol(cptr, NULL, 10);
204                 slots_given = true;
205             }
206         }
207 
208         /* check for local name */
209         if (orte_ifislocal(mini_map[i])) {
210             ndname = orte_process_info.nodename;
211         } else {
212             ndname = mini_map[i];
213         }
214 
215         // Strip off the FQDN if present, ignore IP addresses
216         if( !orte_keep_fqdn_hostnames && !opal_net_isaddr(ndname) ) {
217             if (NULL != (ptr = strchr(ndname, '.'))) {
218                 *ptr = '\0';
219             }
220         }
221         /* remove any modifier */
222         if (NULL != (ptr = strchr(ndname, ':'))) {
223             *ptr = '\0';
224         }
225         /* see if the node is already on the list */
226         found = false;
227         OPAL_LIST_FOREACH(node, &adds, orte_node_t) {
228             if (0 == strcmp(node->name, ndname)) {
229                 found = true;
230                 if (slots_given) {
231                     node->slots += slots;
232                     if (0 < slots) {
233                         ORTE_FLAG_SET(node, ORTE_NODE_FLAG_SLOTS_GIVEN);
234                     }
235                 } else {
236                     ++node->slots;
237                     ORTE_FLAG_SET(node, ORTE_NODE_FLAG_SLOTS_GIVEN);
238                 }
239                 OPAL_OUTPUT_VERBOSE((1, orte_ras_base_framework.framework_output,
240                                      "%s dashhost: node %s already on list - slots %d",
241                                      ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), node->name, node->slots));
242                 break;
243             }
244         }
245 
246         /* If we didn't find it, add it to the list */
247         if (!found) {
248             node = OBJ_NEW(orte_node_t);
249             if (NULL == node) {
250                 opal_argv_free(mapped_nodes);
251                 return ORTE_ERR_OUT_OF_RESOURCE;
252             }
253             node->name = strdup(ndname);
254             OPAL_OUTPUT_VERBOSE((1, orte_ras_base_framework.framework_output,
255                                  "%s dashhost: added node %s to list - slots %d",
256                                  ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), node->name, slots));
257             node->state = ORTE_NODE_STATE_UP;
258             node->slots_inuse = 0;
259             node->slots_max = 0;
260             if (slots_given) {
261                 node->slots = slots;
262                 if (0 < slots) {
263                     ORTE_FLAG_SET(node, ORTE_NODE_FLAG_SLOTS_GIVEN);
264                 }
265             } else if (slots < 0) {
266                 node->slots = 0;
267                 ORTE_FLAG_UNSET(node, ORTE_NODE_FLAG_SLOTS_GIVEN);
268             } else {
269                 node->slots = 1;
270                 ORTE_FLAG_SET(node, ORTE_NODE_FLAG_SLOTS_GIVEN);
271             }
272             opal_list_append(&adds, &node->super);
273         }
274     }
275     opal_argv_free(mini_map);
276 
277     /* transfer across all unique nodes */
278     while (NULL != (item = opal_list_remove_first(&adds))) {
279         nd = (orte_node_t*)item;
280         found = false;
281         for (itm = opal_list_get_first(nodes);
282              itm != opal_list_get_end(nodes);
283              itm = opal_list_get_next(itm)) {
284             node = (orte_node_t*)itm;
285             if (0 == strcmp(nd->name, node->name)) {
286                 found = true;
287                 OPAL_OUTPUT_VERBOSE((1, orte_ras_base_framework.framework_output,
288                                      "%s dashhost: found existing node %s on input list - adding slots",
289                                      ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), node->name));
290                 if (ORTE_FLAG_TEST(nd, ORTE_NODE_FLAG_SLOTS_GIVEN)) {
291                     /* transfer across the number of slots */
292                     node->slots = nd->slots;
293                     ORTE_FLAG_SET(node, ORTE_NODE_FLAG_SLOTS_GIVEN);
294                 }
295                 break;
296             }
297         }
298         if (!found) {
299             OPAL_OUTPUT_VERBOSE((1, orte_ras_base_framework.framework_output,
300                                  "%s dashhost: adding node %s with %d slots to final list",
301                                  ORTE_NAME_PRINT(ORTE_PROC_MY_NAME), nd->name, nd->slots));
302             opal_list_append(nodes, &nd->super);
303         } else {
304             OBJ_RELEASE(item);
305         }
306     }
307 
308     // Managed allocation: Update the node pool slots
309     // with what was asked for in the host list.
310     if(orte_managed_allocation) {
311         orte_node_t *node_from_pool = NULL;
312         for (i = 0; i < orte_node_pool->size; i++) {
313             if (NULL == (node_from_pool = (orte_node_t*)opal_pointer_array_get_item(orte_node_pool, i))) {
314                 continue;
315             }
316             for (itm = opal_list_get_first(nodes);
317                itm != opal_list_get_end(nodes);
318                itm = opal_list_get_next(itm)) {
319               node = (orte_node_t*) itm;
320               if (0 == strcmp(node_from_pool->name, node->name)) {
321                 if(node->slots < node_from_pool -> slots) {
322                   node_from_pool->slots = node->slots;
323                 }
324                 break;
325               }
326               // There's no need to check that this host exists in the pool. That
327               // should have already been checked at this point.
328           }
329       }
330   }
331   rc = ORTE_SUCCESS;
332 
333  cleanup:
334     if (NULL != mapped_nodes) {
335         opal_argv_free(mapped_nodes);
336     }
337     OPAL_LIST_DESTRUCT(&adds);
338 
339     return rc;
340 }
341 
342 
343 /* the -host option can always be used in both absolute
344  * and relative mode, so we have to check for pre-existing
345  * allocations if we are to use relative node syntax
346  */
parse_dash_host(char *** mapped_nodes,char * hosts)347 static int parse_dash_host(char ***mapped_nodes, char *hosts)
348 {
349     orte_std_cntr_t j, k;
350     int rc=ORTE_SUCCESS;
351     char **mini_map=NULL, *cptr;
352     int nodeidx;
353     orte_node_t *node;
354     char **host_argv=NULL;
355 
356     host_argv = opal_argv_split(hosts, ',');
357 
358     /* Accumulate all of the host name mappings */
359     for (j = 0; j < opal_argv_count(host_argv); ++j) {
360         mini_map = opal_argv_split(host_argv[j], ',');
361 
362         for (k = 0; NULL != mini_map[k]; ++k) {
363             if ('+' == mini_map[k][0]) {
364                 /* see if we specified empty nodes */
365                 if ('e' == mini_map[k][1] ||
366                     'E' == mini_map[k][1]) {
367                     /* request for empty nodes - do they want
368                      * all of them?
369                      */
370                     if (NULL != (cptr = strchr(mini_map[k], ':'))) {
371                         /* the colon indicates a specific # are requested */
372                         *cptr = '*';
373                         opal_argv_append_nosize(mapped_nodes, cptr);
374                     } else {
375                         /* add a marker to the list */
376                         opal_argv_append_nosize(mapped_nodes, "*");
377                     }
378                 } else if ('n' == mini_map[k][1] ||
379                            'N' == mini_map[k][1]) {
380                     /* they want a specific relative node #, so
381                      * look it up on global pool
382                      */
383                     nodeidx = strtol(&mini_map[k][2], NULL, 10);
384                     if (nodeidx < 0 ||
385                         nodeidx > (int)orte_node_pool->size) {
386                         /* this is an error */
387                         orte_show_help("help-dash-host.txt", "dash-host:relative-node-out-of-bounds",
388                                        true, nodeidx, mini_map[k]);
389                         rc = ORTE_ERR_SILENT;
390                         goto cleanup;
391                     }
392                     /* if the HNP is not allocated, then we need to
393                      * adjust the index as the node pool is offset
394                      * by one
395                      */
396                     if (!orte_hnp_is_allocated) {
397                         nodeidx++;
398                     }
399                     /* see if that location is filled */
400 
401                     if (NULL == (node = (orte_node_t *) opal_pointer_array_get_item(orte_node_pool, nodeidx))) {
402                         /* this is an error */
403                         orte_show_help("help-dash-host.txt", "dash-host:relative-node-not-found",
404                                        true, nodeidx, mini_map[k]);
405                         rc = ORTE_ERR_SILENT;
406                         goto cleanup;
407                     }
408                     /* add this node to the list */
409                     opal_argv_append_nosize(mapped_nodes, node->name);
410                 } else {
411                     /* invalid relative node syntax */
412                     orte_show_help("help-dash-host.txt", "dash-host:invalid-relative-node-syntax",
413                                    true, mini_map[k]);
414                     rc = ORTE_ERR_SILENT;
415                     goto cleanup;
416                 }
417             } else { /* non-relative syntax - add to list */
418                 /* remove any modifier */
419                 if (NULL != (cptr = strchr(mini_map[k], ':'))) {
420                     *cptr = '\0';
421                 }
422                 /* check for local alias */
423                 if (orte_ifislocal(mini_map[k])) {
424                     opal_argv_append_nosize(mapped_nodes, orte_process_info.nodename);
425                 } else {
426                     opal_argv_append_nosize(mapped_nodes, mini_map[k]);
427                 }
428             }
429         }
430         opal_argv_free(mini_map);
431         mini_map = NULL;
432     }
433 
434 cleanup:
435     if (NULL != host_argv) {
436         opal_argv_free(host_argv);
437     }
438     if (NULL != mini_map) {
439         opal_argv_free(mini_map);
440     }
441     return rc;
442 }
443 
orte_util_filter_dash_host_nodes(opal_list_t * nodes,char * hosts,bool remove)444 int orte_util_filter_dash_host_nodes(opal_list_t *nodes,
445                                      char *hosts,
446                                      bool remove)
447 {
448     opal_list_item_t* item;
449     opal_list_item_t *next;
450     orte_std_cntr_t i, j, len_mapped_node=0;
451     int rc, test;
452     char **mapped_nodes = NULL;
453     orte_node_t *node;
454     int num_empty=0;
455     opal_list_t keep;
456     bool want_all_empty=false;
457     char *cptr;
458     size_t lst, lmn;
459 
460     /* if the incoming node list is empty, then there
461      * is nothing to filter!
462      */
463     if (opal_list_is_empty(nodes)) {
464         return ORTE_SUCCESS;
465     }
466 
467     if (ORTE_SUCCESS != (rc = parse_dash_host(&mapped_nodes, hosts))) {
468         ORTE_ERROR_LOG(rc);
469         return rc;
470     }
471     /* Did we find anything? If not, then do nothing */
472     if (NULL == mapped_nodes) {
473         return ORTE_SUCCESS;
474     }
475 
476     /* NOTE: The following logic is based on knowing that
477      * any node can only be included on the incoming
478      * nodes list ONCE.
479      */
480 
481     len_mapped_node = opal_argv_count(mapped_nodes);
482     /* setup a working list so we can put the final list
483      * of nodes in order. This way, if the user specifies a
484      * set of nodes, we will use them in the order in which
485      * they were specifed. Note that empty node requests
486      * will always be appended to the end
487      */
488     OBJ_CONSTRUCT(&keep, opal_list_t);
489 
490     for (i = 0; i < len_mapped_node; ++i) {
491         /* check if we are supposed to add some number of empty
492          * nodes here
493          */
494         if ('*' == mapped_nodes[i][0]) {
495             /* if there is a number after the '*', then we are
496              * to insert a specific # of nodes
497              */
498             if ('\0' == mapped_nodes[i][1]) {
499                 /* take all empty nodes from the list */
500                 num_empty = INT_MAX;
501                 want_all_empty = true;
502             } else {
503                 /* extract number of nodes to take */
504                 num_empty = strtol(&mapped_nodes[i][1], NULL, 10);
505             }
506             /* search for empty nodes and take them */
507             item = opal_list_get_first(nodes);
508             while (0 < num_empty && item != opal_list_get_end(nodes)) {
509                 next = opal_list_get_next(item);  /* save this position */
510                 node = (orte_node_t*)item;
511                 /* see if this node is empty */
512                 if (0 == node->slots_inuse) {
513                     /* check to see if it is specified later */
514                     for (j=i+1; j < len_mapped_node; j++) {
515                         if (0 == strcmp(mapped_nodes[j], node->name)) {
516                             /* specified later - skip this one */
517                             goto skipnode;
518                         }
519                     }
520                     if (remove) {
521                         /* remove item from list */
522                         opal_list_remove_item(nodes, item);
523                         /* xfer to keep list */
524                         opal_list_append(&keep, item);
525                     } else {
526                         /* mark the node as found */
527                         ORTE_FLAG_SET(node, ORTE_NODE_FLAG_MAPPED);
528                     }
529                     --num_empty;
530                 }
531             skipnode:
532                 item = next;
533             }
534         } else {
535             /* remove any modifier */
536             if (NULL != (cptr = strchr(mapped_nodes[i], ':'))) {
537                 *cptr = '\0';
538             }
539             /* we are looking for a specific node on the list. The
540              * parser will have substituted our local name for any
541              * alias, so we only have to do a strcmp here. */
542             cptr = NULL;
543             lmn = strtoul(mapped_nodes[i], &cptr, 10);
544             item = opal_list_get_first(nodes);
545             while (item != opal_list_get_end(nodes)) {
546                 next = opal_list_get_next(item);  /* save this position */
547                 node = (orte_node_t*)item;
548                 /* search -host list to see if this one is found */
549                 if (orte_managed_allocation &&
550                     (NULL == cptr || 0 == strlen(cptr))) {
551                     /* if we are only given a number, then we test the
552                      * value against the number in the node name. This allows support for
553                      * launch_id-based environments. For example, a hostname
554                      * of "nid0015" can be referenced by "--host 15" */
555                     for (j=strlen(node->name)-1; 0 < j; j--) {
556                         if (!isdigit(node->name[j])) {
557                             j++;
558                             break;
559                         }
560                     }
561                     if (j >= (int)(strlen(node->name) - 1)) {
562                         test = 0;
563                     } else {
564                         lst = strtoul(&node->name[j], NULL, 10);
565                         test = (lmn == lst) ? 0 : 1;
566                     }
567                 } else {
568                     test = strcmp(node->name, mapped_nodes[i]);
569                 }
570                 if (0 == test) {
571                     if (remove) {
572                         /* remove item from list */
573                         opal_list_remove_item(nodes, item);
574                         /* xfer to keep list */
575                         opal_list_append(&keep, item);
576                     } else {
577                         /* mark the node as found */
578                         ORTE_FLAG_SET(node, ORTE_NODE_FLAG_MAPPED);
579                     }
580                     break;
581                 }
582                 item = next;
583             }
584         }
585         /* done with the mapped entry */
586         free(mapped_nodes[i]);
587         mapped_nodes[i] = NULL;
588     }
589 
590     /* was something specified that was -not- found? */
591     for (i=0; i < len_mapped_node; i++) {
592         if (NULL != mapped_nodes[i]) {
593             orte_show_help("help-dash-host.txt", "not-all-mapped-alloc",
594                            true, mapped_nodes[i]);
595             rc = ORTE_ERR_SILENT;
596             goto cleanup;
597         }
598     }
599 
600     if (!remove) {
601         /* all done */
602         rc = ORTE_SUCCESS;
603         goto cleanup;
604     }
605 
606     /* clear the rest of the nodes list */
607     while (NULL != (item = opal_list_remove_first(nodes))) {
608         OBJ_RELEASE(item);
609     }
610 
611     /* the nodes list has been cleared - rebuild it in order */
612     while (NULL != (item = opal_list_remove_first(&keep))) {
613         opal_list_append(nodes, item);
614     }
615 
616     /* did they ask for more than we could provide */
617     if (!want_all_empty && 0 < num_empty) {
618         orte_show_help("help-dash-host.txt", "dash-host:not-enough-empty",
619                        true, num_empty);
620         rc = ORTE_ERR_SILENT;
621         goto cleanup;
622     }
623 
624     rc = ORTE_SUCCESS;
625     /* done filtering existing list */
626 
627 cleanup:
628     for (i=0; i < len_mapped_node; i++) {
629         if (NULL != mapped_nodes[i]) {
630             free(mapped_nodes[i]);
631             mapped_nodes[i] = NULL;
632         }
633     }
634     if (NULL != mapped_nodes) {
635         free(mapped_nodes);
636     }
637 
638     return rc;
639 }
640 
orte_util_get_ordered_dash_host_list(opal_list_t * nodes,char * hosts)641 int orte_util_get_ordered_dash_host_list(opal_list_t *nodes,
642                                          char *hosts)
643 {
644     int rc, i;
645     char **mapped_nodes = NULL;
646     orte_node_t *node;
647 
648     if (ORTE_SUCCESS != (rc = parse_dash_host(&mapped_nodes, hosts))) {
649         ORTE_ERROR_LOG(rc);
650     }
651 
652     /* for each entry, create a node entry on the list */
653     for (i=0; NULL != mapped_nodes[i]; i++) {
654         node = OBJ_NEW(orte_node_t);
655         node->name = strdup(mapped_nodes[i]);
656         opal_list_append(nodes, &node->super);
657     }
658 
659     /* cleanup */
660     opal_argv_free(mapped_nodes);
661     return rc;
662 }
663