o x[h@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/ddl0m1Z1m2Z2zddl3m4Z4Wn e5ye6Z4Ynwz ddl7m8Z8m9Z9dZ:Wn e5ydZ:Ynwe;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-d.eLd/eeVd0eVd1eVfd2d3ZWGd4d5d5eXZYGd6d7d7eYZZd8d9Z[ d}d:eId1eVfd;d<Z\d1ee4fd=d>Z]eJfde^d?eId@eVdAee6fdBdCZ_eJfdAee6fdDdEZ`eJfdAee6fdFdGZadHdIZbd~d?eIfdJdKZcdLeId1eedfdMdNZe   ddLeIdOe^dPe^dQe^d1e^f dRdSZfe!gdTdeMjNddddfd:eId?eeIdUeMdOe^dVe^dQe^dWe^d1e^fdXdYZhGdZd[d[Ziddd\d]eVd^eId_eeLd`eeLd1eeVejff dadbZkdceVd1eeKfdddeZl ddfeVdceVdgeeVd1eUfdhdiZmeMjNddfdfeVd?eIdUeMdPe^dgeeVd1e^f djdkZnd1eVfdldmZoeMjNfdUeMd1eIfdndoZpddpdqZqdrdsZrd1eeVeeTffdtduZsdvdwZtd1eVfdxdyZudzd{ZveGsr&) NotRequired TypedDictc@s<eZdZUeed<ejeed<eed<eeeed<dS) MetaSchemaiddistros frequencyactivate_by_schema_keysN)__name__ __module__ __qualname__str__annotations__typingr r'r"r"r"r%r)Qs r)cs&eZdZdedeffdd ZZS)SchemaDeprecationErrormessageversionc stj|fi|||_dSN)super__init__r6)selfr5r6kwargs __class__r"r%r9\s zSchemaDeprecationError.__init__)r.r/r0r1r9 __classcell__r"r"r<r%r4[s r4c@s,eZdZUeed<eed<defddZdS) SchemaProblempathr5returncCs|jd|jS)N: )r@r5r:r"r"r%formatjszSchemaProblem.formatN)r.r/r0r1r2rDr"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/r0__doc__ CLOUD_CONFIGNETWORK_CONFIGNETWORK_CONFIG_V1NETWORK_CONFIG_V2r"r"r"r%rEqs rElatestc@s(eZdZdZdZdZdZdZddZdS) InstanceDataTypez-Types of instance data provided to cloud-initz user-datarFz vendor-dataz vendor2-datacC|jSr7)valuerCr"r"r%__str__szInstanceDataType.__str__N) r.r/r0rGUSERDATArI VENDORDATA VENDOR2DATArPr"r"r"r%rMs rMc@s&eZdZUeed<eed<eed<dS)InstanceDataPart config_type schema_type config_pathN)r.r/r0rMr2rEr1r"r"r"r%rTs  rTc@seZdZUeed<eed<dS)UserDataTypeAndDecodedContent userdata_typecontentN)r.r/r0r1r2r"r"r"r%rXs  rX, prefix separatorschema_problemsr]r^rAcCs(|tdd|}|r||}|S)NcSs|Sr7)rD)pr"r"r%sz)_format_schema_problems..)joinmap)r_r]r^ formattedr"r"r%_format_schema_problemss recsFeZdZdZ  d deedeeffdd Zdefdd ZZ S) SchemaValidationErrorz.handle_problemszCloud config schema errors: rk"Cloud config schema deprecations: N)rgrhr8r9)r:rgrhrqr<rpr%r9s  zSchemaValidationError.__init__rAcCs t|jSr7)boolrgrCr"r"r% has_errorss z SchemaValidationError.has_errors)NN) r.r/r0rGr SchemaProblemsr9rsrtr>r"r"r<r%rfs"rfc@seZdZdZdS)"SchemaValidationInvalidHeaderErrorz>Raised when no valid header is declared in the user-data file.N)r.r/r0rGr"r"r"r%rvsrvcCs@zddlm}Wn tyYdSw|j|dpt|tfS)zWTYPE_CHECKER override allowing bytes for string type For jsonschema v. 3.0.0+ r)Draft4ValidatorFstring) jsonschemarw ImportError TYPE_CHECKERis_type isinstancebytes)checkerinstancerwr"r"r%is_schema_byte_strings  rconfigcsPdtffdd }|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|}r1d|Sd |d S) Nri _description_versionz z in version .  z **)get capitalizestrip)rkey_descriptionvmsgannotaterr"r%format_messages   z:_add_deprecated_changed_or_new_msg..format_message)r changednewri description)r1rbrcrrstrip)rr filter_keyr filter_keyschanged_new_deprecatedrr"rr%"_add_deprecated_changed_or_new_msgs   rcCsg}d}t|to d|v}|D]\}|rO|d|jdididgvr+|gSt|dr?|jdddkr>||q|jrN|jddkrN||qt|j|kr\||qt|j|krkt|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 best_matches path_depthr|errr"r"r%cloud_init_deepest_matchess2      rrr error_typeccs2|rt|d|gd}|||ddVdSdS)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)rrdeprecated_versiondevelN)rr) _validatorr _instancerrrrr"r"r%r4s rc#sddlm}g}g}d}t|D]U\} } t|j|| | d} ttfdd| } ttfdd| } | s>|| n:t|trad|vrad | d d vrad |d| d vrad }| EdH|| q|sn||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_matchF schema_pathc t| Sr7r}err"r%rao z_anyOf..c t|Sr7rrrr"r%raq r anyOf_typez$refri anyOf_type_TNz.%r is not valid under any of the given schemas)context) jsonschema.exceptionsr enumeratermdescendfilterextendr}rrr) validatoranyOfr_schemarr all_errorsall_deprecationsskip_best_matchindex subschemaall_errserrs deprecationsr"rr%_anyOfHs>     rc#st|}g}g}|D]4\}} tj| |d} ttfdd| } ttfdd| } | s:| } || n|| q t|EdHfdd|D}|rn|| dd d |D}td |fVdS|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`. rcrr7rrrr"r%rarz_oneOf..crr7rrrr"r%rarNcs g|] \}}|r|qSr")is_valid)r#is)rrr"r%r&s z_oneOf..r[cs|]}t|VqdSr7)repr)r#rr"r"r% z_oneOf..z%r is valid under each of %s) rrmrrrrrrbr)roneOfrrr subschemasrrrrrrr first_valid more_validreprsr")rrrr%_oneOfs4       rc Csddlm}m}ddlm}t|j}ddi|dd<i}d|d <|jdt }d |i}t |j }t t d d |t<t t d d |d <t|d<t|d<|d||dd|}ddd}ddd} |} t|drg| } | |_||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)rw FormatChecker)createrrxrlabelFadditionalProperties type_checkerr )rrrrdraft4) meta_schema validatorsr6Nc[s$tdd|||}t|dduS)gOverride version of `is_valid`. It does ignore instances of `SchemaDeprecationError`. cS t|t Sr7r}r4rr"r"r%razFget_jsonschema_validator..is_valid_pre_4_0_0..N)r iter_errorsnextr:rr__rr"r"r%is_valid_pre_4_0_0s  z4get_jsonschema_validator..is_valid_pre_4_0_0c[s*tdd|j|d|}t|dduS)rcSrr7rrr"r"r%rarz.is_valid..)rN)revolverrrr"r"r%rs z*get_jsonschema_validator..is_validrr"r7)ryrwrjsonschema.validatorsrr META_SCHEMAr{redefinerr VALIDATORSrrDEPRECATED_KEYrrrr) rwrrrvalidator_kwargsrrcloudinitValidatorrr is_valid_fnr"r"r%get_jsonschema_validators<      rc Cs|ddlm}z||WdS|y=}z#|r,ttddd|jD|jgd|t d|WYd}~dSd}~ww) 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.cSg|]}t|qSr"r1r#r`r"r"r%r&#z3validate_cloudconfig_metaschema..rgzGMeta-schema validation failed, attempting to validate config anyway: %sN) rr check_schemarfr?rbr@r5LOGwarning)rrthrowrrr"r"r%validate_cloudconfig_metaschema s& rnetwork_configcCs d|vr |ddS|dS)z6Return the version of the network schema when present.networkr6)r)rr"r"r%network_schema_version.s rstrictr log_detailsc CsJtrtdntddSt}tj|d}t|}d|vr%d|i}t |}t ||ddt }g} z| |Wn%t yb} z| tdj| j| jd d | jWYd } ~ nd } ~ wwtj|rnt|| r|r|rt|\} } tt|| | d t| |rt| 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 APIz.)rrcSrr"rrr"r"r%r&rz/validate_cloudconfig_schema..rz#.*\('(?P.*)' was unexpected\)namerz"Deprecated cloud-config provided: rkr r r\r )&cloudinit.net.netplanr%rErIrrKrJ get_schemar!rrrzrrrlrrbr@rrrematchr5r}r4r6rshould_log_deprecationrDEPRECATION_INFO_BOUNDARYrr?reinfor rfrOr)rrrVrr"rr#netplan_availablenetwork_versionrrrrrinfo_deprecations schema_errorr@ prop_matchr5detailsr"r"r%validate_cloudconfig_schemas $               r7c@seZdZdedefddZededeedefdd Zd e fd d Z e ddeedeedeede dede f ddZ deedededeefddZ de de defddZdS) _Annotatororiginal_content schemamarkscCs||_||_dSr7)_original_content _schemamarks)r:r9r:r"r"r%r9%s z_Annotator.__init__titlerZrAcCsd|}d|d|dS)Nr # z: ------------- rj)rb)r=rZbodyr"r"r% _build_footer-s z_Annotator._build_footerr_cCsztt}|D]4\}}td|}|r"|\}}|t||n d}||j|||dur:dj|||d}q|S)Nz&format-l(?P\d+)\.c(?P\d+).*zLine {line} column {col}: {msg})r r r) rrmr,r-groupsintrr<rD)r:r_errors_by_liner@rr-r r r"r"r%_build_errors_by_line2s   z _Annotator._build_errors_by_linerirolabelsfooterr label_prefixcCsB|D]}||}|||d|d||d7}q|S)Nr>rBr')r)rorErFrrGproblemrr"r"r% _add_problemsBs    z_Annotator._add_problemslinesrCdeprecations_by_linec sg}g}g}d}d}t|dD]8\} } || } || } | s| rBg} j| | ||dd}j| | ||dd}|| dd| q|| q|tfddtd dd |fd |ff|S) Nr'E)rGDz # ,cs j|Sr7)r@seqrCr"r%raqrz._Annotator._annotate_content..cSs t|dS)Nr')rsrOr"r"r%rasrErrors Deprecations)rrIrrbrrcr)r:rJrCrKannotated_content error_footerdeprecation_footer error_indexdeprecation_index line_numberr rrrEr"rCr%_annotate_contentQsD    z_Annotator._annotate_contentrgrhcCsF|s|s|jS|jd}||}||}||||}d|S)Nr )r;splitrDrYrb)r:rgrhrJrCrKrSr"r"r%r}s    z_Annotator.annotateN)ri)r.r/r0r1rr9 staticmethodr r@rurDrBrIrYrr"r"r"r%r8$sP   ,r8rgrhr9r:rgrhcCst|||pg|p gS)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 )r8r)r9r:rgrhr"r"r%rs  rrZc Csbddlm}||vr gSg}t|dD]\}}||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_PREFIXr'zformat-lz.c1zIgnored invalid user-data: )cloudinit.handlers.cloud_configr]r splitlines startswithrr?replace)rZr]rline_numr r"r"r%)process_merged_cloud_config_part_problemss"    rcrWinstance_data_pathc Cs:ddlm}m}m}m}t|}d}|dkrnz||||}WnI|y3} z tt|dg| d} ~ w|yN} ztdt | dd WYd} ~ nd} ~ w|yg} ztt | dd WYd} ~ nd} ~ wwd }t|}|s| d \} } } t t|d |d | dd t g|dkrtd|dt||S)a Return tuple of user-data-type and rendered content. When encountering jinja user-data, render said content. :return: UserDataTypeAndDecodedContent :raises: SchemaValidationError when non-jinja content found but header declared ## template: jinja. :raises JinjaSyntaxParsingException when jinja syntax error found. :raises JinjaLoadError when jinja template fails to load. r)JinjaLoadErrorJinjaSyntaxParsingException NotJinjaErrorrender_jinja_payload_from_filez format-l1.c1z text/jinja2zRDetected type '{user_data_type}' from header. But, content is not a jinja templateNz&Failed to render templated user-data. Tsys_exitz format-l2.c1r z!Unrecognized user-data header in z: "z%". Expected first line to be one of: r[text/cloud-configzUser-data type 'z.' not currently evaluated by cloud-init schema)!cloudinit.handlers.jinja_templatererfrgrhrrfr?rr1 partitionrvrbUSERDATA_VALID_HEADERSrrX) rWrZrdrerfrgrhuser_data_typeschema_positionr header_linerr"r"r%&_get_config_type_and_rendered_userdatasb     rrc Csddlm}t|}|std|j|fdS|tjfvr$t|j|}nt|||}|j dvr1dS|j }t |} z|rCt |\} } nt|} i} Wndtjy} zWd} }d}t| drjt| drjt| d}nt| d ryt| d ryt| d }|r|jd} |jd}| td j| |d d |t| t| }|rtt|i|jd || d} ~ wwt| ts|st|jd|d|tjkr| d| stddSt | }|dkrtj!}t"| d|drdS|rtddSn |dkrtj#}t$|}zt%| ||dddstd|jdWdSWdSty[} z9| &r(| | j7} |r7tt|| | | j'dn| j'rGt(| j'ddd}t|| rPt| d | WYd} ~ dSd} ~ ww)aValidate cloudconfig file adheres to a specific jsonschema. @param config_path: Path to the yaml cloud-config file to parse, or None to default to system userdata from Paths object. @param schema: Dict describing a valid jsonschema to validate against. @param schema_type: One of SchemaType.NETWORK_CONFIG or CLOUD_CONFIG @param annotate: Boolean set True to print original config file with error annotations on the offending lines. @param instance_data_path: Path to instance_data JSON, used for text/jinja rendering. :return: True when validation was performed successfully :raises SchemaValidationError containing any of schema_errors encountered. :raises RuntimeError when config_path does not exist. rr$z,Empty '%s' found at %s. Nothing to validate.F)rFrkr'N context_mark problem_markrrzFile {0} is not valid YAML. {1}rrz is not a YAML dict.rz:Skipping network-config schema validation on empty config.r&T)rrrzSSkipping network-config schema validation for version: 2. No netplan API available.)rrVrr#z Skipping z2 schema validation. Jsonschema dependency missing.r\rrr[r\))r*r%rrrOrErIrXrrrYrZrcrryaml safe_load YAMLErrorrgetattrr rrr?rDr1rfrrgr}r RuntimeErrorrrrKr!rJr+r7rtrhre)rWrrVrrdr1decoded_contentdecoded_configrZr cloudconfigr rr rmarkr4r2r5r"r"r%validate_cloudconfig_files             !    r~cCstjtjtjtdS)Nschemas)rr@rbdirnameabspath__file__r"r"r"r%get_schema_dirsrc Cs\tjtt|d}d}z tt|}W|Stt fy-t d|j |iYSw)ziReturn jsonschema for a specific type. Return empty schema when no specific schema file exists. rLNz.r'z3Expected one of --config-file or --system argumentsTrizMWARNING: The --schema-type parameter is inapplicable when --system is presentN) config_filesystemrrrVr)argsexclusive_argsr"r"r%_assert_exclusive_argss  rc Csdtdtdtdtfdd}ztdd}Wn3ttfy7}z|jtkr,td t}nWYd }~nd }~wt yGt}t d Ynw|j rO|j }nt d kr[|d }n|d}g}|jr|jrnt|j}ntj}|tjkrztj}ntj}|t|||jnUt d krtddd||dd}|ttjtj|ttjtj||ddttjtj||ddttjtj|dpdg} | D]} | jrt j| jr|| qt j|d jstd|d jdddd||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_keyrAcSs||pd}tt-t|js(||pd}t|jr0|WdSWd|SWd|S1s;wY|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. riN) get_ipathrFileNotFoundErrorrstatst_size)rrrprimary_datapathraw_pathr"r"r%get_processed_or_fallback_paths     zBget_config_paths_from_args..get_processed_or_fallback_pathtrust)fetch_existing_datasourcez=Using default instance-data/user-data paths for non-root userNzEdatasource not detected, using default instance-data/user-data paths.rrinstance_data_sensitivezNUnable to read system userdata or vendordata as non-root user. Try using sudo.Tri cloud_config userdata_rawvendor_cloud_configvendordata_rawvendor2_cloud_configvendordata2_rawrriz Config file z does not existz Error: {}fmtrj)rr1rrrerrnorrrrrrrgetuidrrrVrErHrIrMrQrrTrrRrSrrWr@r) rrrrrd config_filesrVinstancedata_type userdata_filesupplemental_config_files data_partr"r"r%get_config_paths_from_argss             rc Cst|t}t|\}}d}tt|dk}|r)tdddd|Dd}g}t|dD]\}} d} |rHtd |d | jd | j d | j t j krTt| j } n|} z t | j | | j |j|} Wn^ty} z'|jst|d | jd| j tt| |dd|| jWYd} ~ q0d} ~ wty} z t|d | jtt| |dd|| jWYd} ~ q0d} ~ ww| r|jr| j } nt| j} t|d| q0|rtddd|DddddSdS)z@Handle provided schema args and perform the appropriate actions.rir'z!Found cloud-config data types: %sr[css|]}t|jVqdSr7)r1rU)r#cfg_partr"r"r%rsz%handle_schema_args..z Fr rz at :zInvalid rz Error: {} )rNz Valid schema csrr7r)r#rr"r"r%rrzError: Invalid schema: {} Tr)rr+rrsrrrbrrUrWrVrErIr~rrfrr1rryr)r)rrrdrnested_output_prefixmulti_config_output error_typesidxrperformed_schema_validation cfg_schemarcfgr"r"r%handle_schema_argsts     rcOstjtdtjddddS)zProvide a stub for backwards compatibility. This function is no longer used, but earlier versions of modules required this function for documentation purposes. This is a stub so that custom modules do not break on upgrade. z24.4zbThe 'get_meta_doc()' function is deprecated and will be removed in a future version of cloud-init.r")loggerr6requested_levelrrri)rlog_with_downgradable_levelrloggingWARNING)_args_kwargsr"r"r% get_meta_docs rcCst}td|dS)zDTool to validate schema of a cloud-config file or print schema docs.rr)rr parse_argsrr"r"r%mainsr__main__)FN)T)FFTr7)xrGrrrrr,rsys collectionsr contextlibrcopyrrrrr functoolsrr3rr r r r r rrru cloudinitrrrrcloudinit.cmd.develrcloudinit.handlersrrcloudinit.helpersrcloudinit.log.log_utilrcloudinit.sourcesrcloudinit.temp_utilsrcloudinit.utilrrryrrz Exceptionnetplanrrr getLoggerr.rUSERDATA_SCHEMA_FILENETWORK_CONFIG_V1_SCHEMA_FILENETWORK_CONFIG_V2_SCHEMA_FILErrlkeysrntyping_extensionsr'r(r)rr4r?rurErHrIrJrKrrMrTrXr1re ValueErrorrfrvrrrrsrrrrrrBrr!timedr7r8r~rrcrrr~rr+rrrrrrexitr"r"r"r%s      (              ) #'  H *V#  _ m   ! H  ; }@