a 'Dg @sdZddlZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZdd lmZmZmZmZmZmZmZmZddlZdd lmZm Z m!Z!m"Z"dd l#m$Z$dd l%m&Z&m'Z'dd l(m)Z)ddl*m+Z+ddl,m-Z-ddl.m/Z/ddl0m1Z1m2Z2zddl3m4Z4Wne5yPe6Z4Yn0zddl7m8Z8m9Z9dZ:Wne5ydZ:Yn0e;e<Z=dZ>dZ?dZ@dZAeBdde&CDZDerddlZddlEmFZFmGZGGdddeGZHneIZHGdd d e4ZJGd!d"d"eZKeeKZLGd#d$d$eZMeMjNd%e>ieMjOd%e@ieMjPd%e?ieMjQd%e@iiZRGd&d'd'eZSGd(d)d)eZTGd*d+d+eZUdd,d-eLeeVeVeVd.d/d0ZWGd1d2d2eXZYGd3d4d4eYZZd5d6Z[dueIeVd7d8d9Z\ee4d:d;d<Z]eJfe^eIeVee6d=d>d?Z_ee_dd@Z`ee_dAd@ZaeJfee6dBdCdDZbeJfee6dBdEdFZcdGdHZddveIdIdJdKZeeIeefdLdMdNZgdweIe^e^e^e^dOdPdQZhe!idRdeMjNddddfeIeeIeMe^e^e^e^e^dSdTdUZjGdVdWdWZkdddXeVeIeeLeeLeeVelfdYdZd[ZmeVeeKd\d]d^ZndxeVeVeeVeUd_d`daZoeMjNddfeVeIeMe^eeVe^dbdcddZpeVd:dedfZqeMjNfeMeIdgdhdiZrdydjdkZsdldmZteeVeeTfd:dndoZudpdqZvdrdsZweGr&) NotRequired TypedDictc@s<eZdZUeed<ejeed<eed<eeeed<dS) MetaSchemaidZdistrosZ frequencyZactivate_by_schema_keysN)__name__ __module__ __qualname__str__annotations__typingr r(r"r"r"r%r*Qs r*cs$eZdZeedfdd ZZS)SchemaDeprecationError)messageversionc stj|fi|||_dSN)super__init__r4)selfr3r4kwargs __class__r"r%r7\szSchemaDeprecationError.__init__)r,r-r.r/r7 __classcell__r"r"r:r%r2[sr2c@s,eZdZUeed<eed<edddZdS) SchemaProblempathr3returncCs|jd|jS)N: )r>r3r8r"r"r%formatjszSchemaProblem.formatN)r,r-r.r/r0rCr"r"r"r%r=fs r=c@s eZdZdZdZdZdZdZdS) SchemaTypeaSupported schema types are either cloud-config or network-config. Vendordata and Vendordata2 format adheres to cloud-config schema type. Cloud Metadata is unique schema to each cloud platform and likely will not be represented in this enum. z cloud-confignetwork-configznetwork-config-v1znetwork-config-v2N)r,r-r.__doc__ CLOUD_CONFIGNETWORK_CONFIGNETWORK_CONFIG_V1NETWORK_CONFIG_V2r"r"r"r%rDqs rDlatestc@s(eZdZdZdZdZdZdZddZdS) InstanceDataTypez-Types of instance data provided to cloud-initz user-datarEz vendor-dataz vendor2-datacCs|jSr5)valuerBr"r"r%__str__szInstanceDataType.__str__N) r,r-r.rFUSERDATArH VENDORDATA VENDOR2DATArNr"r"r"r%rLs rLc@s&eZdZUeed<eed<eed<dS)InstanceDataPart config_type schema_type config_pathN)r,r-r.rLr0rDr/r"r"r"r%rRs rRc@seZdZUeed<eed<dS)UserDataTypeAndDecodedContent userdata_typecontentN)r,r-r.r/r0r"r"r"r%rVs rV, prefix separator)schema_problemsr[r\r@cCs(|tdd|}|r$||}|S)NcSs|Sr5)rC)pr"r"r%r'z)_format_schema_problems..)joinmap)r]r[r\ formattedr"r"r%_format_schema_problemss rccs@eZdZdZd eeeedfdd ZedddZZ S) SchemaValidationErrorz.handle_problemszCloud config schema errors: rj"Cloud config schema deprecations: N)rfrgr6r7)r8rfrgrpr:ror%r7s  zSchemaValidationError.__init__r?cCs t|jSr5)boolrfrBr"r"r% has_errorssz SchemaValidationError.has_errors)NN) r,r-r.rFr SchemaProblemsr7rrrsr<r"r"r:r%rds"rdc@seZdZdZdS)"SchemaValidationInvalidHeaderErrorz>Raised when no valid header is declared in the user-data file.N)r,r-r.rFr"r"r"r%rusrucCs@zddlm}Wnty$YdS0|j|dp>t|tfS)zWTYPE_CHECKER override allowing bytes for string type For jsonschema v. 3.0.0+ r)Draft4ValidatorFstring) jsonschemarv ImportError TYPE_CHECKERis_type isinstancebytes)Zcheckerinstancervr"r"r%is_schema_byte_strings  r)configr@csPtdfdd }|r|ngd}dt||}dd}||S)zcombine description with new/changed/deprecated message deprecated/changed/new keys require a _version key (this is verified in a unittest), a _description key is optional keycsr|sdS|dd}|dd|d}|d|d|}rbd|Sd |d S) NrhZ _description_versionz z in version .  z **)get capitalizestrip)rZkey_descriptionvmsgannotaterr"r%format_messages   z:_add_deprecated_changed_or_new_msg..format_message)r changednewrh description)r/r`rarrstrip)rr filter_keyrZ filter_keysZchanged_new_deprecatedrr"rr%"_add_deprecated_changed_or_new_msgs  rr?cCsg}d}t|tod|v}|D]}|r|d|jdididgvrV|gSt|dr~|jdddkr||q|jr|jddkr||qt|j|kr||qt|j|krt|j}|g}q|S)zReturn the best_match errors based on the deepest match in the json_path This is useful for anyOf and oneOf subschemas where the most-specific error tends to be the most appropriate. rtype propertiesenum json_pathN) r|dictschemarhasattrrappendr>len)errorsr~Z best_matchesZ path_depthr{errr"r"r%cloud_init_deepest_matchess,      r)r rr error_typeccs,|r(t|d|gd}|||ddVdS)zJsonschema validator for `deprecated` items. It yields an instance of `error_type` if deprecated that must be handled, otherwise the instance is consider faulty. T)rrZdeprecated_versiondevelN)rr) _validatorr Z _instancerrrrr"r"r%r4s r)rrrc#sddlm}g}g}d}t|D]\} } t|j|| | d} ttfdd| } ttfdd| } | sz|| qt|trd|vrd | d d vrd |d| d vrd }| EdH|| q |s||Vt d|f|dV|EdHdS)aJsonschema validator for `anyOf`. It treats occurrences of `error_type` as non-errors, but yield them for external processing. Useful to process schema annotations, as `deprecated`. Cloud-init's network schema under the `config` key has a complexity of allowing each list dict item to declare it's type with a `type` key which can contain the values: bond, bridge, nameserver, physical, route, vlan. This schema 'flexibility' makes it hard for the default jsonschema.exceptions.best_match function to find the correct schema failure because it typically returns the failing schema error based on the schema of greatest match depth. Since each anyOf dict matches the same depth into the network schema path, `best_match` just returns the first set of schema errors, which is almost always incorrect. To find a better schema match when encountering schema validation errors, cloud-init network schema introduced schema $defs with the prefix `anyOf_type_`. If the object we are validating contains a 'type' key, and one of the failing schema objects in an anyOf clause has a name of the format anyOf_type_XXX, raise those schema errors instead of calling best_match. r) best_matchFZ schema_pathcs t| Sr5r|err"r%r_sr'z_anyOf..cs t|Sr5rrrr"r%r_ur'rZ anyOf_typez$refrhZ anyOf_type_TNz.%r is not valid under any of the given schemas)context) jsonschema.exceptionsr enumeraterldescendfilterextendr|rrr) validatoranyOfr~_schemarr all_errorsall_deprecationsZskip_best_matchindex subschemaall_errserrs deprecationsr"rr%_anyOfLs@     rc#st|}g}g}|D]h\}} tj| |d} ttfdd| } ttfdd| } | sr| } || q|| qt|EdHfdd|D}|r|| dd d |D}td |fVn |EdHdS) zJsonschema validator for `oneOf`. It treats occurrences of `error_type` as non-errors, but yield them for external processing. Useful to process schema annotations, as `deprecated`. rcs t| Sr5rrrr"r%r_r'z_oneOf..cs t|Sr5rrrr"r%r_r'Ncs g|]\}}|r|qSr")is_valid)r#is)r~rr"r%r&r'z_oneOf..rYcss|]}t|VqdSr5)repr)r#rr"r"r% r'z_oneOf..z%r is valid under each of %s) rrlrrrrrr`r)roneOfr~rrZ subschemasrrrrrrrZ first_validZ more_validZreprsr")rr~rr%_oneOfs2      rc Csddlm}m}ddlm}t|j}ddi|dd<i}d|d <|jdt }d |i}t |j }t |t <t|d <t|d <t|d <|f||dd|}ddd}ddd} |} t|dr| } | |_||fS)zGet metaschema validator and format checker Older versions of jsonschema require some compatibility changes. @returns: Tuple: (jsonschema.Validator, FormatChecker) @raises: ImportError when jsonschema is not present r)rv FormatChecker)createrrwrlabelFadditionalProperties type_checkerrrrZdraft4) meta_schema validatorsr4Nc[s$tdd|||}t|dduS)gOverride version of `is_valid`. It does ignore instances of `SchemaDeprecationError`. cSs t|t Sr5r|r2rr"r"r%r_szFget_jsonschema_validator..is_valid_pre_4_0_0..N)r iter_errorsnextr8r~r__rr"r"r%is_valid_pre_4_0_0s  z4get_jsonschema_validator..is_valid_pre_4_0_0c[s*tdd|j|d|}t|dduS)rcSs t|t Sr5rrr"r"r%r_sz.is_valid..rN)revolverrrr"r"r%rs z*get_jsonschema_validator..is_validr)N)N)rxrvrZjsonschema.validatorsrrZ META_SCHEMArzZredefinerrZ VALIDATORS_validator_deprecatedDEPRECATED_KEY_validator_changedrrrr) rvrrrZvalidator_kwargsrrcloudinitValidatorrrZ is_valid_fnr"r"r%get_jsonschema_validators<     rrc Cs|ddlm}z||Wn\|yv}zD|rVttddd|jD|jgd|t d|WYd}~n d}~00dS) a Validate provided schema meets the metaschema definition. Return strict Validator and FormatChecker for use in validation @param validator: Draft4Validator instance used to validate the schema @param schema: schema to validate @param throw: Sometimes the validator and checker are required, even if the schema is invalid. Toggle for whether to raise SchemaValidationError or log warnings. @raises: ImportError when jsonschema is not present @raises: SchemaValidationError when the schema is invalid r) SchemaError.cSsg|] }t|qSr"r/r#r^r"r"r%r&'r'z3validate_cloudconfig_metaschema..rfzGMeta-schema validation failed, attempting to validate config anyway: %sN) rrZ check_schemardr=r`r>r3LOGwarning)rrthrowrrr"r"r%validate_cloudconfig_metaschemas" r)network_configr@cCs d|vr|ddS|dS)z6Return the version of the network schema when present.networkr4)r)rr"r"r%network_schema_version2sr)rstrictr log_detailsr@c CsRtrtdntddSt}tj|d}t|}d|vrJd|i}t |}t ||ddt }g} z| |WnJt y} z2| tdj| j| jd d | jWYd } ~ n d } ~ 00tj|rt|| rN|r|rt|\} } tt|| | d t| |r6t| tjjd dd} ntjjd} t| dS)ajOn systems with netplan, validate network_config schema for file Leverage NetplanParser for error annotation line, column and detailed errors. @param network_config: Dict of network configuration settings validated against @param strict: Boolean, when True raise SchemaValidationErrors instead of logging warnings. @param annotate: Boolean, when True, print original network_config_file content with error annotations @param log_details: Boolean, when True logs details of validation errors. If there are concerns about logging sensitive userdata, this should be set to False. @return: True when schema validation was performed. False when not on a system with netplan and netplan python support. @raises: SchemaValidationError when netplan's parser raises NetplanParserExceptions. z*Validating network-config with netplan APIzr`rrdumpsrrZload_yaml_hierarchyrrr=rCrcolumnr3existsshutilrmtreeload_with_marksprintannotated_cloudconfig_filerdrcrDrHrMr)rrrrZ parse_dirZ netplan_fileZnet_cfgZ src_contentparserrr_marksr3r"r"r%netplan_validate_network_schema9s`        rzValidating schema)rrrTrstrict_metaschemarlog_deprecationsr@cCsFddlm}|tjkrDt|}|dkr.tj}n|dkr|jdkr>|j|kr>td|j}|r>|d}t|tr|jdksht|jtjr|| t||jn|t||jq| t||jq|r|rt |dd}t !|| rt | dd}t "||r| s| s|rt#| | || rB|r,t | |j$ddd}n |j$d}t %|dS)aPValidate provided config meets the schema definition. @param config: Dict of cloud configuration settings validated against schema. Ignored if strict_metaschema=True @param schema: jsonschema dict describing the supported schema definition for the cloud config module (config.cc_*). If None, validate against global schema. @param schema_type: Optional SchemaType. One of: SchemaType.CLOUD_CONFIG or SchemaType.NETWORK_CONFIG_V1 or SchemaType.NETWORK_CONFIG_V2 Default: SchemaType.CLOUD_CONFIG @param strict: Boolean, when True raise SchemaValidationErrors instead of logging warnings. @param strict_metaschema: Boolean, when True validates schema using strict metaschema definition at runtime (currently unused) @param log_details: Boolean, when True logs details of validation errors. If there are concerns about logging sensitive userdata, this should be set to False. @param log_deprecations: Controls whether to log deprecations or not. @raises: SchemaValidationError when provided config does not validate against the provided schema. @raises: RuntimeError when provided config sourced from YAML is not a dict. @raises: ValueError on invalid schema_type not in CLOUD_CONFIG or NETWORK_CONFIG_V1 or NETWORK_CONFIG_V2 r available)rrrTFN)rz5Ignoring schema validation. jsonschema is not present)Zformat_checkercSs|jSr5)r>rr"r"r%r_r'z-validate_cloudconfig_schema..rrcSsg|] }t|qSr"rrr"r"r%r&r'z/validate_cloudconfig_schema..rz#.*\('(?P.*)' was unexpected\)namerz"Deprecated cloud-config provided: rjrrrZr)&cloudinit.net.netplanrrDrHrrJrI get_schemarrrryrrrkrr`r>rrrematchr3r|r2r4rZshould_log_deprecationrZDEPRECATION_INFO_BOUNDARYrr=rcinfor rdrMr)rrrTrrrrnetplan_availablenetwork_versionrrrrrZinfo_deprecations schema_errorr>Z prop_matchr3detailsr"r"r%validate_cloudconfig_schemas$              r c @seZdZeedddZeeeeedddZe ddd Z edeeeeeee ee d d d Z eeeeeedddZ e e edddZdS) _Annotator)original_content schemamarkscCs||_||_dSr5)_original_content _schemamarks)r8rrr"r"r%r7)sz_Annotator.__init__)titlerXr@cCsd|}d|d|dS)Nr# z: ------------- ri)r`)rrXbodyr"r"r% _build_footer1s z_Annotator._build_footer)r]cCsztt}|D]h\}}td|}|rD|\}}|t||nd}||j|||dur dj|||d}q |S)Nz&format-l(?P\d+)\.c(?P\d+).*zLine {line} column {col}: {msg})rrr) rrlrrgroupsintrrrC)r8r]errors_by_liner>rrrrr"r"r%_build_errors_by_line6s   z _Annotator._build_errors_by_linerh)rnlabelsfooterr label_prefixr@cCsB|D]8}||}|||d|d||d7}q|S)NrrAr)r)rnrrrrZproblemrr"r"r% _add_problemsFs    z_Annotator._add_problems)linesrdeprecations_by_liner@c sg}g}g}d}d}t|dD]p\} } || } || } | s>| rg} j| | ||dd}j| | ||dd}|| dd| q|| q|tfddtd dd |fd |ff|S) NrE)rDz # ,cs j|Sr5)rseqrBr"r%r_ur'z._Annotator._annotate_content..cSs t|dS)Nr)rrr$r"r"r%r_wr'ZErrorsZ Deprecations)rrrr`rrar)r8rrr annotated_contentZ error_footerZdeprecation_footerZ error_indexZdeprecation_indexZ line_numberrrrrr"rBr%_annotate_contentUsD    z_Annotator._annotate_content)rfrgr@cCsF|s|s|jS|jd}||}||}||||}d|S)Nr)rsplitrr'r`)r8rfrgrrr r&r"r"r%rs   z_Annotator.annotateN)rh)r,r-r.r/rr7 staticmethodr rrtrrrr'rr"r"r"r%r(s2  .rre)rrrfrgr@cCst|||pg|pgS)aReturn contents of the cloud-config file annotated with schema errors. @param cloudconfig: YAML-loaded dict from the original_content or empty dict if unparsable. @param original_content: The contents of a cloud-config file @param schemamarks: Dict with schema marks. @param schema_errors: Instance of `SchemaProblems`. @param schema_deprecations: Instance of `SchemaProblems`. @return Annotated schema )rr)rrrfrgr"r"r%rs  r)rXr@c Csbddlm}||vrgSg}t|dD]2\}}||r*|td|d||dq*|S)aAnnotate and return schema validation errors in merged cloud-config.txt When merging multiple cloud-config parts cloud-init logs an error and ignores any user-data parts which are declared as #cloud-config but cannot be processed. the handler.cloud_config module also leaves comments in the final merged config for every invalid part file which begin with MERGED_CONFIG_SCHEMA_ERROR_PREFIX to aid in triage. r)MERGED_PART_SCHEMA_ERROR_PREFIXrzformat-lz.c1zIgnored invalid user-data: )Zcloudinit.handlers.cloud_configr*r splitlines startswithrr=replace)rXr*rZline_numrr"r"r%)process_merged_cloud_config_part_problemss    r.)rUrXinstance_data_pathr@c CsLddlm}m}m}m}t|}d}|dkrz||||}Wn|yr} ztt|dg| WYd} ~ nrd} ~ 0|y} z tdt | dd WYd} ~ nr`dirnameabspath__file__r"r"r"r%get_schema_dirsrA)rTr@c Cs^tjtt|d}d}ztt|}Wn*tt fyXt d|j |iYS0|S)ziReturn jsonschema for a specific type. Return empty schema when no specific schema file exists. rKNzr`rASCHEMA_FILES_BY_TYPEjsonloadsrIOErrorOSErrorrrrM)rTZ schema_file full_schemar"r"r%rs rcCs|stjddd}|jdddd|jdd ttjjtjjgd tjd |jd d tdt dd|jddddd|jddddd|S)z0Return a parser for supported cmdline arguments.cloudconfig-schemazSchema validation and documentation of instance-data configuration provided to cloud-init. This includes: user-data, vendor-data and network-config)progrz-cz --config-filez@Path of the cloud-config or network-config YAML file to validate)helpz-tz --schema-typezSWhen providing --config-file, the schema type to validate config against. Default: )rchoicesrJz-iz--instance-datazbPath to instance-data.json file for variable expansion of '##template: jinja' user-data. Default: instance_data)rrJz--system store_trueFzVValidate the system instance-data provided as vendor-data user-data and network-config)actiondefaultrJz --annotatez;Annotate existing instance-data files any discovered errors) argparseArgumentParser add_argumentr/rDrGrMrHr get_runpathrr"r"r% get_parsersT    rUcCsF|j|jg}tdd|Ddkr.tddd|jrB|jrBtddS) z:Error or warn on invalid exclusive parameter combinations.cSsg|] }|r|qSr"r")r#argr"r"r%r&r'z*_assert_exclusive_args..rz3Expected one of --config-file or --system argumentsTr4zMWARNING: The --schema-type parameter is inapplicable when --system is presentN) config_filesystemrrrTr)argsZexclusive_argsr"r"r%_assert_exclusive_argss  rZc Csttttddd}ztdd}Wnfttfyh}z*|jtkrRtdt}nWYd}~n*d}~0t yt}t dYn0|j r|j }n"t d kr|d }n |d }g}|jr |jrt|j}ntj}|tjkrtj}ntj}|t|||jnt d kr&td d d||dd}|ttjtj|ttjtj||ddttjtj||ddttjtj|dpdg} | D](} | jrt j| jr|| qt j|d jstd|d jddd d||fS)aReturn appropriate instance-data.json and instance data parts Based on command line args, and user permissions, determine the appropriate instance-data.json to source for jinja templates and a list of applicable InstanceDataParts such as user-data, vendor-data and network-config for which to validate schema. Avoid returning any InstanceDataParts when the expected config_path does not exist. :return: A tuple of the instance-data.json path and a list of viable InstanceDataParts present on the system. )pathsprimary_path_keyraw_fallback_path_keyr@cSsr||p d}ttHt|jsP||p0d}t|jrP|WdSWdn1sd0Y|S)akGet processed data path when non-empty of fallback to raw data path. - When primary path and raw path exist and are empty, prefer primary path. - When primary path is empty but the raw fallback path is non-empty, this indicates an invalid and ignored raw user-data was provided and cloud-init emitted a warning and did not process unknown raw user-data. In the case of invalid raw user-data header, prefer raw_fallback_path_key so actionable sensible warnings can be reported to the user about the raw unparsable user-data. rhN) get_ipathrFileNotFoundErrorrstatst_size)r[r\r]Zprimary_datapathZraw_pathr"r"r%get_processed_or_fallback_path s   0zBget_config_paths_from_args..get_processed_or_fallback_pathZtrust)Zfetch_existing_datasourcez=Using default instance-data/user-data paths for non-root userNzEdatasource not detected, using default instance-data/user-data paths.rrLZinstance_data_sensitivezNUnable to read system userdata or vendordata as non-root user. Try using sudo.Tr4Z cloud_configZ userdata_rawZvendor_cloud_configZvendordata_rawZvendor2_cloud_configZvendordata2_rawrrhz Config file z does not existz Error: {}fmtr5)rr/rrErFerrnorrrrrrLrgetuidrSrWrTrDrGrHrLrOrrRrrPrQr^rUr>r) rYrbr[rr/ config_filesrTZinstancedata_typeZ userdata_fileZsupplemental_config_filesZ data_partr"r"r%get_config_paths_from_argss          rhc Cst|t}t|\}}d}tt|dk}|rRtdddd|Dd}g}t|dD]Z\}} d} |rtd |d | jd | j d | j t j krt| j } n|} zt | j | | j |j|} Wnty2} zP|jst|d | jd| j tt| |dd|| jWYd} ~ q`d} ~ 0ty} z@t|d | jtt| |dd|| jWYd} ~ q`d} ~ 00| r`|jr| j } n t| j} t|d| q`|rtddd|DddddS)z@Handle provided schema args and perform the appropriate actions.rhrz!Found cloud-config data types: %srYcss|]}t|jVqdSr5)r/rS)r#cfg_partr"r"r%rr'z%handle_schema_args..z Frrz at :zInvalid rz Error: {} )rdNz Valid schema css|]}t|VqdSr5r)r#rr"r"r%rr'zError: Invalid schema: {} Trc)rZrrhrrrrr`rrSrUrTrDrHr=rrdrr/rr<rW)rrYrGr/rgZnested_output_prefixZmulti_config_outputZ error_typesidxriZperformed_schema_validationZ cfg_schemarZcfgr"r"r%handle_schema_argsxsv    " rlcCst}td|dS)zDTool to validate schema of a cloud-config file or print schema docs.rHr)rUrl parse_argsrTr"r"r%mainsrn__main__)FN)T)FFT)N)N)yrFrPrCZloggingrrrsys collectionsr contextlibrcopyrrrrer functoolsrr1rr r r r r rrr:Z cloudinitrrrrZcloudinit.cmd.develrZcloudinit.handlersrrZcloudinit.helpersrZcloudinit.log.log_utilrZcloudinit.sourcesrZcloudinit.temp_utilsrZcloudinit.utilrrrxrry ExceptionZnetplanrrrZ getLoggerr,rZUSERDATA_SCHEMA_FILEZNETWORK_CONFIG_V1_SCHEMA_FILEZNETWORK_CONFIG_V2_SCHEMA_FILErrkkeysr8Ztyping_extensionsr(r)r*rr2r=rtrDrGrHrIrJrBrLrRrVr/rc ValueErrorrdrurrrrrrrrrrrrrrrZtimedr rr}rr.r9r=rArrUrZrhrlrnexitr"r"r"r%sX      (            ) #'    H *V#  _m  ! H  ; }@