7272import stat
7373import copy_with_port_portname
7474import numpy as np
75- import shlex # Added for POSIX shell escaping
75+ import shlex # Added for POSIX shell escaping
76+ import posixpath
7677
7778# input validation helper
7879def safe_name (value , context , allow_path = False ):
@@ -92,6 +93,36 @@ def safe_name(value, context, allow_path=False):
9293 if re .search (pattern , value ):
9394 raise ValueError (f"Unsafe { context } : '{ value } ' contains illegal characters." )
9495 return value
96+
97+ def _normalize_relpath (value ):
98+ return value .replace ("\\ " , "/" )
99+
100+ def safe_relpath (value , context ):
101+ """
102+ Validates a relative path for node source files.
103+ Allows subdirectories, but blocks traversal, absolute paths, and drive roots.
104+ """
105+ if not value :
106+ raise ValueError (f"{ context } cannot be empty" )
107+ normalized = _normalize_relpath (value )
108+ safe_name (normalized , context , allow_path = True )
109+ if normalized .startswith ("/" ) or normalized .startswith ("~" ):
110+ raise ValueError (f"Unsafe { context } : absolute paths are not allowed." )
111+ if re .match (r"^[A-Za-z]:" , normalized ):
112+ raise ValueError (f"Unsafe { context } : drive paths are not allowed." )
113+ if ":" in normalized :
114+ raise ValueError (f"Unsafe { context } : ':' is not allowed in relative paths." )
115+ parts = normalized .split ("/" )
116+ if any (part == "" for part in parts ):
117+ raise ValueError (f"Unsafe { context } : empty path segment is not allowed." )
118+ if any (part == ".." for part in parts ):
119+ raise ValueError (f"Unsafe { context } : path traversal ('..') is not allowed." )
120+ return normalized
121+
122+ def ensure_parent_dir (path ):
123+ parent = os .path .dirname (path )
124+ if parent :
125+ os .makedirs (parent , exist_ok = True )
95126
96127MKCONCORE_VER = "22-09-18"
97128
@@ -248,14 +279,15 @@ def cleanup_script_files():
248279 node_label = re .sub (r'(\s+|\n)' , ' ' , node_label )
249280
250281 #Validate node labels
251- if ':' in node_label :
252- container_part , source_part = node_label .split (':' , 1 )
253- safe_name (container_part , f"Node container name '{ container_part } '" )
254- safe_name (source_part , f"Node source file '{ source_part } '" )
255- else :
256- safe_name (node_label , f"Node label '{ node_label } '" )
257- # Explicitly reject incorrect format to prevent later crashes and ambiguity
258- raise ValueError (f"Invalid node label '{ node_label } ': expected format 'container:source' with a ':' separator." )
282+ if ':' in node_label :
283+ container_part , source_part = node_label .split (':' , 1 )
284+ safe_name (container_part , f"Node container name '{ container_part } '" )
285+ normalized_source = safe_relpath (source_part , f"Node source file '{ source_part } '" )
286+ node_label = f"{ container_part } :{ normalized_source } "
287+ else :
288+ safe_name (node_label , f"Node label '{ node_label } '" )
289+ # Explicitly reject incorrect format to prevent later crashes and ambiguity
290+ raise ValueError (f"Invalid node label '{ node_label } ': expected format 'container:source' with a ':' separator." )
259291
260292 nodes_dict [node ['id' ]] = node_label
261293 node_id_to_label_map [node ['id' ]] = node_label .split (':' )[0 ]
@@ -373,10 +405,10 @@ def cleanup_script_files():
373405 logging .warning (f"Error processing edge for parameter aggregation: { e } " )
374406
375407# --- Now, run the specialization for each node that has aggregated parameters ---
376- if node_edge_params :
377- logging .info ("Running script specialization process..." )
378- specialized_scripts_output_dir = os .path .abspath (os .path .join (outdir , "src" ))
379- os .makedirs (specialized_scripts_output_dir , exist_ok = True )
408+ if node_edge_params :
409+ logging .info ("Running script specialization process..." )
410+ specialized_scripts_output_dir = os .path .abspath (os .path .join (outdir , "src" ))
411+ os .makedirs (specialized_scripts_output_dir , exist_ok = True )
380412
381413 for node_id , params_list in node_edge_params .items ():
382414 current_node_full_label = nodes_dict [node_id ]
@@ -388,23 +420,33 @@ def cleanup_script_files():
388420 if not original_script or "." not in original_script :
389421 continue # Skip if not a script file
390422
391- template_script_full_path = os .path .join (sourcedir , original_script )
392- if not os .path .exists (template_script_full_path ):
393- logging .error (f"Cannot specialize: Original script '{ template_script_full_path } ' not found in '{ sourcedir } '." )
394- continue
395-
396- new_script_basename = copy_with_port_portname .run_specialization_script (
397- template_script_full_path ,
398- specialized_scripts_output_dir ,
399- params_list ,
400- python_executable ,
401- copy_script_py_path
402- )
403-
404- if new_script_basename :
405- # Update nodes_dict to point to the new comprehensive specialized script
406- nodes_dict [node_id ] = f"{ container_name } :{ new_script_basename } "
407- logging .info (f"Node ID '{ node_id } ' ('{ container_name } ') updated to use specialized script '{ new_script_basename } '." )
423+ template_script_full_path = os .path .join (sourcedir , original_script )
424+ if not os .path .exists (template_script_full_path ):
425+ logging .error (f"Cannot specialize: Original script '{ template_script_full_path } ' not found in '{ sourcedir } '." )
426+ continue
427+
428+ script_subdir = posixpath .dirname (original_script )
429+ node_output_dir = specialized_scripts_output_dir
430+ if script_subdir :
431+ node_output_dir = os .path .join (specialized_scripts_output_dir , script_subdir )
432+ os .makedirs (node_output_dir , exist_ok = True )
433+
434+ new_script_basename = copy_with_port_portname .run_specialization_script (
435+ template_script_full_path ,
436+ node_output_dir ,
437+ params_list ,
438+ python_executable ,
439+ copy_script_py_path
440+ )
441+
442+ if new_script_basename :
443+ # Update nodes_dict to point to the new comprehensive specialized script
444+ if script_subdir :
445+ new_script_relpath = posixpath .join (script_subdir , new_script_basename )
446+ else :
447+ new_script_relpath = new_script_basename
448+ nodes_dict [node_id ] = f"{ container_name } :{ new_script_relpath } "
449+ logging .info (f"Node ID '{ node_id } ' ('{ container_name } ') updated to use specialized script '{ new_script_relpath } '." )
408450 else :
409451 logging .error (f"Failed to generate specialized script for node ID '{ node_id } '. It will retain its original script." )
410452
@@ -446,22 +488,25 @@ def cleanup_script_files():
446488 else :
447489 dockername , langext = sourcecode , ""
448490
449- script_target_path = os .path .join (outdir , "src" , sourcecode )
491+ script_target_path = os .path .join (outdir , "src" , sourcecode )
492+ ensure_parent_dir (script_target_path )
450493
451494 # If the script was specialized, it's already in outdir/src. If not, copy from sourcedir.
452495 if node_id_key not in node_edge_params :
453496 script_source_path = os .path .join (sourcedir , sourcecode )
454- if os .path .exists (script_source_path ):
455- shutil .copy2 (script_source_path , script_target_path )
456- else :
457- logging .error (f"Script '{ sourcecode } ' not found in sourcedir '{ sourcedir } '" )
497+ if os .path .exists (script_source_path ):
498+ shutil .copy2 (script_source_path , script_target_path )
499+ else :
500+ logging .error (f"Script '{ sourcecode } ' not found in sourcedir '{ sourcedir } '" )
458501
459502 # The rest of the file handling (Dockerfiles, .dir) uses 'dockername',
460503 # which is now derived from the specialized script name, maintaining consistency.
461504 if concoretype == "docker" :
462505 custom_dockerfile = f"Dockerfile.{ dockername } "
463- if os .path .exists (os .path .join (sourcedir , custom_dockerfile )):
464- shutil .copy2 (os .path .join (sourcedir , custom_dockerfile ), os .path .join (outdir , "src" , custom_dockerfile ))
506+ if os .path .exists (os .path .join (sourcedir , custom_dockerfile )):
507+ dest_dockerfile = os .path .join (outdir , "src" , custom_dockerfile )
508+ ensure_parent_dir (dest_dockerfile )
509+ shutil .copy2 (os .path .join (sourcedir , custom_dockerfile ), dest_dockerfile )
465510
466511 dir_for_node = f"{ dockername } .dir"
467512 if os .path .isdir (os .path .join (sourcedir , dir_for_node )):
@@ -640,11 +685,15 @@ def cleanup_script_files():
640685 try :
641686 containername , sourcecode = node_label .split (':' , 1 )
642687 if not sourcecode or "." not in sourcecode : continue
643- dockername = os .path .splitext (sourcecode )[0 ]
644- with open (os .path .join (outdir , "src" , f"{ dockername } .iport" ), "w" ) as fport :
645- fport .write (str (ports ['iport' ]).replace ("'" + prefixedgenode , "'" ))
646- with open (os .path .join (outdir , "src" , f"{ dockername } .oport" ), "w" ) as fport :
647- fport .write (str (ports ['oport' ]).replace ("'" + prefixedgenode , "'" ))
688+ dockername = os .path .splitext (sourcecode )[0 ]
689+ iport_path = os .path .join (outdir , "src" , f"{ dockername } .iport" )
690+ oport_path = os .path .join (outdir , "src" , f"{ dockername } .oport" )
691+ ensure_parent_dir (iport_path )
692+ ensure_parent_dir (oport_path )
693+ with open (iport_path , "w" ) as fport :
694+ fport .write (str (ports ['iport' ]).replace ("'" + prefixedgenode , "'" ))
695+ with open (oport_path , "w" ) as fport :
696+ fport .write (str (ports ['oport' ]).replace ("'" + prefixedgenode , "'" ))
648697 except ValueError :
649698 continue
650699
@@ -653,10 +702,11 @@ def cleanup_script_files():
653702if (concoretype == "docker" ):
654703 for node in nodes_dict :
655704 containername ,sourcecode = nodes_dict [node ].split (':' )
656- if len (sourcecode )!= 0 and sourcecode .find ("." )!= - 1 : #3/28/21
657- dockername ,langext = sourcecode .split ("." )
658- if not os .path .exists (outdir + "/src/Dockerfile." + dockername ): # 3/30/21
659- try :
705+ if len (sourcecode )!= 0 and sourcecode .find ("." )!= - 1 : #3/28/21
706+ dockername ,langext = sourcecode .split ("." )
707+ dockerfile_path = os .path .join (outdir , "src" , f"Dockerfile.{ dockername } " )
708+ if not os .path .exists (dockerfile_path ): # 3/30/21
709+ try :
660710 if langext == "py" :
661711 src_path = CONCOREPATH + "/Dockerfile.py"
662712 logging .info ("assuming .py extension for Dockerfile" )
@@ -677,8 +727,9 @@ def cleanup_script_files():
677727 except :
678728 logging .error (f"{ CONCOREPATH } is not correct path to concore" )
679729 quit ()
680- with open (outdir + "/src/Dockerfile." + dockername ,"w" ) as fcopy :
681- fcopy .write (source_content )
730+ ensure_parent_dir (dockerfile_path )
731+ with open (dockerfile_path ,"w" ) as fcopy :
732+ fcopy .write (source_content )
682733 if langext == "py" :
683734 fcopy .write ('CMD ["python", "-i", "' + sourcecode + '"]\n ' )
684735 if langext == "m" :
@@ -695,7 +746,7 @@ def cleanup_script_files():
695746 containername ,sourcecode = nodes_dict [node ].split (':' )
696747 if len (sourcecode )!= 0 and sourcecode .find ("." )!= - 1 : #3/28/21
697748 dockername ,langext = sourcecode .split ("." )
698- fbuild .write ("mkdir docker-" + dockername + "\n " )
749+ fbuild .write ("mkdir -p docker-" + dockername + "\n " )
699750 fbuild .write ("cd docker-" + dockername + "\n " )
700751 fbuild .write ("cp ../src/Dockerfile." + dockername + " Dockerfile\n " )
701752 #copy sourcefiles from ./src into corresponding directories
@@ -922,36 +973,48 @@ def cleanup_script_files():
922973if concoretype == "posix" :
923974 fbuild .write ('#!/bin/bash' + "\n " )
924975
925- for node in nodes_dict :
926- containername ,sourcecode = nodes_dict [node ].split (':' )
927- if len (sourcecode )!= 0 :
928- if sourcecode .find ("." )== - 1 :
929- logging .error ("cannot pull container " + sourcecode + " with control core type " + concoretype ) #3/28/21
930- quit ()
931- dockername ,langext = sourcecode .split ("." )
932- fbuild .write ('mkdir ' + containername + "\n " )
933- if concoretype == "windows" :
934- fbuild .write ("copy .\\ src\\ " + sourcecode + " .\\ " + containername + "\\ " + sourcecode + "\n " )
935- if langext == "py" :
936- fbuild .write ("copy .\\ src\\ concore.py .\\ " + containername + "\\ concore.py\n " )
937- elif langext == "cpp" :
938- # 6/22/21
939- fbuild .write ("copy .\\ src\\ concore.hpp .\\ " + containername + "\\ concore.hpp\n " )
940- elif langext == "v" :
941- # 6/25/21
942- fbuild .write ("copy .\\ src\\ concore.v .\\ " + containername + "\\ concore.v\n " )
943- elif langext == "m" : # 4/2/21
944- fbuild .write ("copy .\\ src\\ concore_*.m .\\ " + containername + "\\ \n " )
945- fbuild .write ("copy .\\ src\\ import_concore.m .\\ " + containername + "\\ \n " )
946- fbuild .write ("copy .\\ src\\ " + dockername + ".iport .\\ " + containername + "\\ concore.iport\n " )
947- fbuild .write ("copy .\\ src\\ " + dockername + ".oport .\\ " + containername + "\\ concore.oport\n " )
948- #include data files in here if they exist
949- if os .path .isdir (sourcedir + "/" + dockername + ".dir" ):
950- fbuild .write ("copy .\\ src\\ " + dockername + ".dir\\ *.* .\\ " + containername + "\n " )
951- else :
952- fbuild .write ("cp ./src/" + sourcecode + " ./" + containername + "/" + sourcecode + "\n " )
953- if langext == "py" :
954- fbuild .write ("cp ./src/concore.py ./" + containername + "/concore.py\n " )
976+ for node in nodes_dict :
977+ containername ,sourcecode = nodes_dict [node ].split (':' )
978+ if len (sourcecode )!= 0 :
979+ if sourcecode .find ("." )== - 1 :
980+ logging .error ("cannot pull container " + sourcecode + " with control core type " + concoretype ) #3/28/21
981+ quit ()
982+ dockername ,langext = sourcecode .split ("." )
983+ if concoretype == "windows" :
984+ fbuild .write ('mkdir ' + containername + "\n " )
985+ else :
986+ fbuild .write ("mkdir -p ./" + containername + "\n " )
987+ source_subdir = posixpath .dirname (sourcecode )
988+ if source_subdir :
989+ if concoretype == "windows" :
990+ source_subdir_win = source_subdir .replace ("/" , "\\ " )
991+ fbuild .write ("mkdir .\\ " + containername + "\\ " + source_subdir_win + "\n " )
992+ else :
993+ fbuild .write ("mkdir -p ./" + containername + "/" + source_subdir + "\n " )
994+ if concoretype == "windows" :
995+ sourcecode_win = sourcecode .replace ("/" , "\\ " )
996+ dockername_win = dockername .replace ("/" , "\\ " )
997+ fbuild .write ("copy .\\ src\\ " + sourcecode_win + " .\\ " + containername + "\\ " + sourcecode_win + "\n " )
998+ if langext == "py" :
999+ fbuild .write ("copy .\\ src\\ concore.py .\\ " + containername + "\\ concore.py\n " )
1000+ elif langext == "cpp" :
1001+ # 6/22/21
1002+ fbuild .write ("copy .\\ src\\ concore.hpp .\\ " + containername + "\\ concore.hpp\n " )
1003+ elif langext == "v" :
1004+ # 6/25/21
1005+ fbuild .write ("copy .\\ src\\ concore.v .\\ " + containername + "\\ concore.v\n " )
1006+ elif langext == "m" : # 4/2/21
1007+ fbuild .write ("copy .\\ src\\ concore_*.m .\\ " + containername + "\\ \n " )
1008+ fbuild .write ("copy .\\ src\\ import_concore.m .\\ " + containername + "\\ \n " )
1009+ fbuild .write ("copy .\\ src\\ " + dockername_win + ".iport .\\ " + containername + "\\ concore.iport\n " )
1010+ fbuild .write ("copy .\\ src\\ " + dockername_win + ".oport .\\ " + containername + "\\ concore.oport\n " )
1011+ #include data files in here if they exist
1012+ if os .path .isdir (sourcedir + "/" + dockername + ".dir" ):
1013+ fbuild .write ("copy .\\ src\\ " + dockername_win + ".dir\\ *.* .\\ " + containername + "\n " )
1014+ else :
1015+ fbuild .write ("cp ./src/" + sourcecode + " ./" + containername + "/" + sourcecode + "\n " )
1016+ if langext == "py" :
1017+ fbuild .write ("cp ./src/concore.py ./" + containername + "/concore.py\n " )
9551018 elif langext == "cpp" :
9561019 fbuild .write ("cp ./src/concore.hpp ./" + containername + "/concore.hpp\n " )
9571020 elif langext == "v" :
0 commit comments