+
    „WÝjµ`  ã                  ó�  a € 0 t $ R t^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
Ht ^ RIHt ]P                  ! ]4      t^t^t^t^tRtRtRt^t^tRtR	t ! R
 R]4      tR R ltRtRAR R llt ]PB                  ! ^R7      R R l4       t"R R lt#RAR R llt$]PB                  ! ^R7      R R l4       t%RBR R llt&R R lt'RCR R llt(R  R! lt)R" R# lt*R$ R% lt+R& R' lt,R(RR)R*/R+ R, llt-R- R. lt.R/R*R(R/R0 R1 llt/R2 R3 lt0]1! 4       t2R4]3R5&   R6 R7 lt4RDR8R*R9R*R(R/R: R; lllt5]6! 4       3R< R= llt7R>R/R? R@ llt8R# )Ea|  Directories an elevated Windows process creates must stay usable by the same user unelevated.

Since CPython 3.13, ``os.mkdir(path, 0o700)`` on Windows applies a protected DACL of exactly
SYSTEM, Administrators and OWNER RIGHTS. An elevated process's default owner is
``BUILTIN\Administrators``, so OWNER RIGHTS resolves to Administrators too, and the user's own
non-elevated token (where Administrators is deny-only) gets ``WinError 5`` on the directory and on
everything created inside it (#139845, #139485, the ``gateway-locks`` reports in #134889 W2). A plain
``mkdir`` inherits its parent's ACL instead, which under ``%LOCALAPPDATA%`` names the user, so the
``mode=0o700`` sites are the ones that strand the user.

:func:`make_user_dir` is the one place Hermes state/lock/log directories are created. Off Windows,
or in a non-elevated process, it is exactly ``Path.mkdir``. In an elevated Windows process it adds
an explicit, inheriting full-control ACE for the token's user SID to the directory, so the directory
and every child created later stay usable by that user after elevation ends. An elevated call on an
existing directory re-grants the same ACE, which repairs a directory an earlier elevated run left
behind; the pywin32 write does not propagate, so when it had to write, the entries already inside
(an old token lock) are granted too, once, never through a link. A non-elevated process cannot change such a DACL; callers that hit one report it once with
:func:`unusable_dir_remedy`.

An elevated grant never goes through a junction, symlink or other reparse point (nor a hard-linked
file): same-user medium-integrity code can swap any entry under the home for a junction to e.g.
``C:\Program Files\X``, and a grant that followed it would hand the user full control of the
target across the UAC boundary. The path is lstat-checked first, and the pywin32 grant opens it with
``FILE_FLAG_OPEN_REPARSE_POINT`` and re-checks the handle, so a swap after the check is not followed
either; the icacls fallback passes ``/L``. :func:`grant_tree_user_access` is the recursive form
(the post-update repair), pruning every reparse point instead of descending into it.
)ÚannotationsN)ÚIterator)ÚPathi   i   i   i   i    c                  ó   € ] tR t^7tRtRtR# )ÚReparsePointRefusedzVThe grant target is (or became) a junction, symlink, other reparse point or hard link.© N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__static_attributes__r   ó    ÚBC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_dir_acl.pyr   r   7   s   † Ý`r   r   c               ó    € V ^8„  d   QhRRRR/# )é   Úpathú
str | PathÚreturnÚboolr   )Úformats   "r   Ú__annotate__r   ;   s   € ÷ 
1ñ 
1˜:ð 
1¨$ñ 
1r   c                óL  €  \         P                  ! V 4      p\        P                  ! TP
                  4      '       g   \        TR^ 4      \        ,          '       d   R# \        \         P                  RR4      p\        T;'       d	    T! T 4      4      #   \         d     R# i ; i)z³True when *path* itself is a symlink, junction or other reparse point (never followed), or
cannot be lstat-ed at all; the safe answer for "may an elevated grant act on this path".TÚst_file_attributesÚ
isjunctionN)
ÚosÚlstatÚOSErrorÚstatÚS_ISLNKÚst_modeÚgetattrÚFILE_ATTRIBUTE_REPARSE_POINTr   r   )r   Ústr   s   &  r   Úis_reparse_pointr$   ;   s   € ðÜ�XŠX�d‹^ˆô ‡|‚|�B—J‘J×Ò¤7¨2Ð/CÀQÓ#GÔJf×#fÔ#fÙÜœŸ™ ,°Ó5€JÜ�
×/Ð/™z¨$Ó/Ó0Ð0øô ô Úðús   ‚B ÂB#Â"B#i   c               ó    € V ^8„  d   QhRRRR/# )r   Úpidz
int | Noner   úbool | Noner   )r   s   "r   r   r   K   s   € ÷ #(ñ #(˜:ð #(°ñ #(r   c           	     ó@  € ^ RI p^ RI Hp VP                  RRR7      pVP                  RRR7      pVP                  VP                  n        VP                  VP                  n        VP                  VP                  VP                  .VP                  n	        VP                  VP                  VP                  VP                  4      .VP                  n	        VP                  VP                  VP                  VP                  VP                  VP                  4      .VP                  n	        VP                  .VP                  n	        V RJ pV'       d   VP	                  4       MVP                  \         RV 4      pV'       g   R# VP                  4       p VP                  V\"        VP%                  V4      4      '       g8    V'       d   VP                  V4       V'       g   VP                  V4       R# R# VP                  4       VP                  4       r˜VP                  V\&        VP%                  V4      VP)                  V4      VP%                  V	4      4      '       g8    V'       d   VP                  V4       V'       g   VP                  V4       R# R# \+        VP,                  4      V'       d   VP                  V4       V'       g   VP                  V4       # #   T'       d   TP                  T4       T'       g   TP                  T4       i i ; i)ac  ``TokenElevation`` of *pid*'s token (default: this process). The ONE elevation probe: the
grants below, the refused-owner naming in ``gateway.runtime_bootstrap_windows`` and the
Windows service installer all read it. None when the process or its token cannot be opened
(across an elevation boundary that refusal is itself an answer the caller interprets).N)ÚwintypesÚadvapi32T)Úuse_last_errorÚkernel32F)Úctypesr)   ÚWinDLLÚHANDLEÚGetCurrentProcessÚrestypeÚOpenProcessÚDWORDÚBOOLÚargtypesÚPOINTERÚOpenProcessTokenÚc_intÚc_void_pÚGetTokenInformationÚCloseHandleÚ"_PROCESS_QUERY_LIMITED_INFORMATIONÚ_TOKEN_QUERYÚbyrefÚ_TOKEN_ELEVATIONÚsizeofr   Úvalue)
r&   r-   r)   ÚadvapiÚkernelÚownÚprocessÚtokenÚelevatedÚsizes
   &         r   Úprocess_elevationrI   K   sd  € ó
 Ýà�]‰]˜:°dˆ]Ó;€FØ�]‰]˜:°dˆ]Ó;€FØ'/§¡€F×ÑÔ$Ø!)§¡€F×ÑÔØ#+§>¡>°8·=±=À(Ç.Á.Ð"Q€F×ÑÔØ(0¯©¸¿¹ÈÏÉÐX`×XgÑXgÓIhÐ'i€F×ÑÔ$Ø+3¯?©?¸F¿L¹LÈ&Ï/É/Ð[c×[iÑ[iØ+1¯>©>¸(¿.¹.Ó+Ið+K€F×ÑÔ'à#+§?¡?Ð"3€F×ÑÔØ
�ˆ+€Cß,/ˆf×&Ñ&Ô(°V×5GÑ5GÔHjØINÐPSó6U€GçÙØ�O‰OÓ€Eð(Ø×&Ñ& w´¸f¿l¹lÈ5Ó>Q×RÒRØ÷ Ø×Ñ˜uÔ%ßØ×Ñ˜wÖ'ñ ð "Ÿ™Ó)¨8¯>©>Ó+;�$Ø×)Ñ)¨%Ô1AÀ6Ç<Á<ÐPXÓCYØ*0¯-©-¸Ó*AÀ6Ç<Á<ÐPTÓCU÷Wò Wà÷ Ø×Ñ˜uÔ%ßØ×Ñ˜wÖ'ñ ô	 �H—N‘NÓ#çØ×Ñ˜uÔ%ßØ×Ñ˜wÕ'ð ø÷ Ø×Ñ˜uÔ%ßØ×Ñ˜wÕ'ð ús   Æ+K( Ç>A+K( Ê K( Ë(!LÌ
L)Úmaxsizec               ó   € V ^8„  d   QhRR/# )r   r   r   r   )r   s   "r   r   r   r   s   € ÷ %ñ %˜Tñ %r   c                 óV   € \         P                  R8w  d   R# \        \        4       4      # )zWTrue for an elevated (UAC full-token) Windows process; False elsewhere or on any error.Úwin32F)ÚsysÚplatformr   rI   r   r   r   Úprocess_is_elevatedrP   q   s"   € ô ‡|�|�wÔÙÜÔ!Ó#Ó$Ð$r   c               ó    € V ^8„  d   QhRRRR/# )r   r   Ústrr   r   )r   s   "r   r   r   y   s   € ÷ (ñ (˜cð ( cñ (r   c                ór   € V P                  R 4      '       d   RV R,          ,           # V P                  R4      # )z\\?\UNC\z\\:é   NNz\\?\)Ú
startswithÚremoveprefix)r   s   &r   Ú_strip_win32_prefixrW   y   s1   € Ø‡��~×&Ò&Ø˜˜R�Õ Ð Ø×Ñ˜YÓ'Ð'r   c               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r   r   Úis_dirr   Úexpectedú
str | Noner   r   )r   s   "r   r   r      s)   € ÷ -ñ -˜dð -¨Dð -¸Jð -ÐRVñ -r   c           	     ó´  € ^ RI p^ RIp^ RIp^ RIpVP	                  VP                  4       VP                  4      pVP                  WvP                  4      ^ ,          pVP                  \        V 4      \        \        ,          \        R\        \        \         ,          R4      p	 VeŽ   \#        VP%                  V	^ 4      4      p
\&        P(                  P+                  V
P-                  R4      4      \&        P(                  P+                  VP-                  R4      4      8w  d   \/        V  RV
 RV 24      hVP1                  V	4      pV^ ,          V^,          rÜV\2        ,          '       d   \/        V  R24      hV\4        ,          '       g   V^8”  d   \/        V  RV R24      h\7        V\4        ,          4      pVP9                  W–P:                  4      pVP=                  4       pV'       d   VP>                  VP@                  ,          M^ pVe½   \C        VPE                  4       4       Fž  pVPG                  V4      R,          w  vpppppVVPH                  8X  g   K5  VV8X  g   K>  VVPJ                  ,          VPJ                  8X  g   Kb  VV,          V8X  g   Kr  VVPL                  ,          '       d   K�   V	PO                  4        R	# 	  MVPQ                  4       pVPS                  VPT                  VVPJ                  V4       VPW                  R
VR	4       VPY                  W–P:                  V4        V	PO                  4        R
#   T	PO                  4        i ; i)zºWrite the ACE on the handle; True when it was written (False: already present). The write
does NOT propagate (``SetKernelObjectSecurity``), so a written dir's existing children are owed.NÚ\z resolves to z, not ú. is a junction, symlink or other reparse pointz is a hard link (z' names); its other names may be outside:Né   NFT)-ÚntsecurityconÚwin32apiÚ	win32fileÚwin32securityr7   r0   ÚTOKEN_QUERYr:   Ú	TokenUserÚ
CreateFilerR   Ú_READ_CONTROLÚ
_WRITE_DACÚ_FILE_SHARE_ALLÚ_OPEN_EXISTINGÚ_FILE_FLAG_BACKUP_SEMANTICSÚ_FILE_FLAG_OPEN_REPARSE_POINTrW   ÚGetFinalPathNameByHandler   r   ÚnormcaseÚrstripr   ÚGetFileInformationByHandler"   ÚFILE_ATTRIBUTE_DIRECTORYr   ÚGetKernelObjectSecurityÚDACL_SECURITY_INFORMATIONÚGetSecurityDescriptorDaclÚOBJECT_INHERIT_ACEÚCONTAINER_INHERIT_ACEÚrangeÚGetAceCountÚGetAceÚACCESS_ALLOWED_ACE_TYPEÚFILE_ALL_ACCESSÚINHERIT_ONLY_ACEÚCloseÚACLÚAddAccessAllowedAceExÚACL_REVISION_DSÚSetSecurityDescriptorDaclÚSetKernelObjectSecurity)r   rY   rZ   r`   ra   rb   rc   rF   Úuser_sidÚhandleÚfinalÚinfoÚ
attributesÚlinksÚ
descriptorÚdaclÚinheritÚindexÚace_typeÚflagsÚ_ÚmaskÚsids   &&&                    r   Ú_grant_with_pywin32r’      s¹  € ó ÛÛÛà×*Ñ*¨8×+EÑ+EÓ+GÈ×IbÑIbÓc€EØ×0Ñ0°×8OÑ8OÓPÐQRÕS€Hð ×!Ñ!¤# d£)¬]¼ZÕ-GÌÐZ^Ô`nÜ"=Ô@]Õ"]Ð_cóe€FðØÒä'¨	×(JÑ(JÈ6ÐSTÓ(UÓVˆEÜ�w‰w×Ñ §¡¨TÓ 2Ó3´r·w±w×7GÑ7GÈÏÉÐX\ÓH]Ó7^Ô^Ü)¨T¨F°-À¸wÀfÈXÈJÐ*WÓXÐXØ×3Ñ3°FÓ;ˆØ  �G T¨!¥W�EØÔ4×4Ô4Ü%¨¨Ð.\Ð&]Ó^Ð^ØÔ4×4Ô4¸À¼Ü%¨¨Ð.?À¸wÐFmÐ&nÓoÐoÜ�jÔ#;Õ;Ó<ˆØ"×:Ñ:¸6×CjÑCjÓkˆ
Ø×3Ñ3Ó5ˆß^d�=×3Ñ3°m×6YÑ6YÖYÐjkˆØÒÜ˜t×/Ñ/Ó1Ö2�Ø37·;±;¸uÓ3EÀbÕ3IÑ0Ð%�˜5 1 t¨SØ × EÑ EÖEÈ#ÐQYÎ/Ø  =×#@Ñ#@Õ@ÀM×DaÑDaÖaØ! G�O¨wÖ6¸uÀ}×GeÑGe×?eÖ?eØ ð 	�‰Žò 3ð !×$Ñ$Ó&ˆDØ×"Ñ" =×#@Ñ#@À'È=×KhÑKhÐjrÔsØ×,Ñ,¨T°4¸Ô?à×-Ñ-¨f×6]Ñ6]Ð_iÔjØà�‰Žøˆ�‰�ús9   Â$C&M ÆCM ÉM É&M Ê
M ÊM Ê5M ËA*M ÍMc               ó   € V ^8„  d   QhRR/# )r   r   rR   r   )r   s   "r   r   r   °   s   € ÷ ñ �Sñ r   c            
     ó\  € \         P                  ! . R	ORRRRR^R7      p V P                  P                  4       P	                  R^4      R
,          P                  4       P                  R4      pVP                  4       P                  R4      '       g   \        RV P                  : 24      hV# )ÚwhoamiTzutf-8Úreplace)Úcapture_outputÚtextÚcheckÚencodingÚerrorsÚtimeoutÚ,Ú"zS-1-z4could not resolve the current user SID from whoami: )r•   z/userz/foÚcsvz/nhéÿÿÿÿ)Ú
subprocessÚrunÚstdoutÚstripÚrsplitÚupperrU   r   )r•   r‘   s     r   Ú_whoami_sidr§   ¯   s�   € ä�^Š^ÒDÐUYÐ`dØ"&°ÀÐTVôX€Fà
�-‰-×
Ñ
Ó
×
&Ñ
& s¨AÓ
.¨rÕ
2×
8Ñ
8Ó
:×
@Ñ
@ÀÓ
E€CØ�9‰9‹;×!Ñ! &×)Ò)ÜÐLÈVÏ]É]ÑL]Ð^Ó_Ð_Ø€Jr   c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   r   r   rY   r   rZ   r[   Úsubtree_has_linkr'   r   ÚNoner   )r   s   "r   r   r   ¹   s1   € ÷ +ñ +˜Tð +¨4ð +¸:ð +Ø)4ð+Ø@Dñ+r   c                óú  € Vf   \        V  R24      hV'       d   V'       d   \        V  R24      hV'       dp   Vfl   . p\        W4       UUu. uF$  w  rVV'       d   K  \        V4      '       g   K"  VNK&  	  pppV'       g	   V'       d   \        WG,           ^ ,           RV  R24      hV'       d   R\        4        R2MR\        4        R2p\        P
                  ! R	\        V 4      R
VRR.RR^<R7       R# u uppi )zP*subtree_has_link*: the caller's own walk already answered it (None: walk here).Nz; has no trusted root to check (icacls fallback, no pywin32)z9 holds a junction, symlink or hard link (icacls fallback)z under z6 is a junction, symlink or hard link (icacls fallback)Ú*z
:(OI)(CI)Fz:FÚicaclsz/grantz/Qz/LT)r—   r™   rœ   )r   Ú iter_tree_without_reparse_pointsÚ_hard_linked_filer§   r¡   r¢   rR   )	r   rY   rZ   r©   ÚnestedÚentryÚentry_is_dirÚlinkedÚaces	   &&&&     r   Ú_grant_with_icaclsrµ   ¹   s  € ð Òô " T FÐ*eÐ"fÓgÐgß×"Ü! T FÐ*cÐ"dÓeÐeßÐ"Ò*ð  ˆÜ3SÐTXÔ3aô DÑ3aÑ/˜EÜ%ô Ü*;¸E×*B÷ �%Ñ3aˆñ Dç—VÜ%¨&­/¸1Õ)=Ð(>¸gÀdÀVð L?ð '?ó @ð @ç+1ˆAŒk‹mˆ_˜JÑ
'¸¼;»=¸/ÈÐ7L€Cô ‡N‚N�Hœc $›i¨°3¸¸dÐCÐTXØ r÷+ùóDs   ÁC7ÁC7Á1C7c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   r   r   )r   s   "r   r   r   ×   s   € ÷ <ñ <˜Dð < Tñ <r   c                óÌ   €  \         P                  ! V 4      p\        P                  ! TP
                  4      '       * ;'       d    TP                  ^8„  #   \         d     R # i ; i)T)r   r   r   r   ÚS_ISDIRr    Úst_nlink©r   r#   s   & r   r¯   r¯   ×   sP   € ðÜ�XŠX�d‹^ˆô �|Š|˜BŸJ™JÓ'Ô'×;Ð;¨B¯K©K¸!©OÐ;øô ô Úðús   ‚A ÁA#Á"A#c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R/# )
r   r   r   rY   r   rZ   r[   r©   r'   r   r   )r   s   "r   r   r   ß   s1   € ÷ ñ �ð ˜tð °jð Ø(ðØ48ñr   c                óÌ   € \        V 4      '       g   \        V 4      '       d   \        V  R24      h \        WV4      ;'       d    T#   \         d    \        YY#4        R# i ; i)a–  Grant the token's user full control on *path* itself; raise :class:`ReparsePointRefused`
for a reparse point or hard-linked file (or, with *expected*, a handle that resolves anywhere
else) and anything else on failure.

Returns True when the existing children are still owed: the pywin32 write does not propagate,
while the icacls fallback (``SetNamedSecurityInfo``) does, and refuses a dir holding a link.z9 is a junction, symlink, other reparse point or hard linkF)r$   r¯   r   r’   ÚImportErrorrµ   )r   rY   rZ   r©   s   &&&&r   Ú_grantr¾   ß   sc   € ô ˜×ÒÔ!2°4×!8Ò!8Ü! T FÐ*cÐ"dÓeÐeðÜ" 4°Ó:×EÐE¸vÐEøÜô Ü˜4¨ÔDÚðús   ²A ÁA ÁA#Á"A#c               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   ú
list[Path]r   )r   s   "r   r   r   ð   s   € ÷ 
Qñ 
Q ð 
Q¨*ñ 
Qr   c                ó¾  €  ^ RI Hp \        \        P                  P                  V! 4       4      4      p\        \        P                  P                  V 4      4      P                  V4      p\        ^\        TP                  4      ^,           4       Uu. uF  qBP                  ! TP                  RT !  NK!  	  up#   \        \        \        3 d    . u # i ; iu upi )a  *path* and each ancestor of it strictly below the Hermes home (lexically), outermost first;
empty when *path* is not under the home. The home itself (and above) is the user's own choice
and may legitimately be a link; an entry INSIDE it is what same-user code can swap.)Úget_hermes_homeN)Úhermes_constantsrÂ   r   r   r   ÚabspathÚrelative_tor½   Ú
ValueErrorr   rw   ÚlenÚpartsÚjoinpath)r   rÂ   ÚhomeÚrelÚis   &    r   Ú_components_below_homerÍ   ð   s£   € ðÝ4Ü”B—G‘G—O‘O¡OÓ$5Ó6Ó7ˆÜ”2—7‘7—?‘? 4Ó(Ó)×5Ñ5°dÓ;ˆô 49¸¼CÀÇ	Á	»NÈQÕ<NÔ3OÓPÑ3O¨a�MŠM˜3Ÿ9™9 R a˜=Ô)Ñ3OÑPÐPøô œ¤WÐ-ô ØŠ	ðüâPs   ‚A*B< Â%CÂ<CÃCc               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   r[   r   )r   s   "r   r   r   ý   s   € ÷ fñ f˜$ð f :ñ fr   c                óN  € \        V 4      pV'       g   R# V^ ,          P                  p\        P                  P                  ! \        P                  P                  V4      .\        \        P                  P                  V 4      4      P                  V4      P                  O5!  # )z´Where *path* must resolve when nothing below the home is a link: the home's real path plus
the relative parts. None off-home (only the leaf's own open-reparse check applies there).N)
rÍ   Úparentr   r   ÚjoinÚrealpathr   rÄ   rÅ   rÈ   )r   Ú
componentsrÊ   s   &  r   Ú_expected_finalrÔ   ý   sn   € ô (¨Ó-€JßÙØ�a�=×Ñ€DÜ�7‰7�<Š<œŸ™×(Ñ(¨Ó.Ðe´´b·g±g·o±oÀdÓ6KÓ1L×1XÑ1XÐY]Ó1^×1dÑ1dÓeÐer   c               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   ÚexcÚBaseExceptionr   rª   r   )r   s   "r   r   r     s&   € ÷ Uñ U˜ð U =ð U°Tñ Ur   c                ó2   € \         P                  R W4       R# )zÛNot granting the current user access to %s while elevated (%s): an elevated grant never follows a junction or link out of the Hermes home. If this is your own link, point HERMES_HOME (or the link) at the real directory.N)ÚlogÚwarning)r   rÖ   s   &&r   Ú_warn_refusedrÛ     s   € Ü‡K�Kð IàJNöUr   c               ó    € V ^8„  d   QhRRRR/# )r   rG   r'   r   r   r   )r   s   "r   r   r     s   € ÷ Cñ C˜ð C¨ñ Cr   c                ó"   € V f   \        4       # T # )z¤The host fact every grant decision takes: *elevated* as passed, else probed (an elevated
Windows process; :func:`process_is_elevated` is False on every other host).)rP   )rG   s   &r   Ú	_elevatedrÞ     s   € ð %-Ò$4ÔÓ ÐB¸(ÐBr   rG   Úrepair_childrenFc               ó(   € V ^8„  d   QhRRRRRRRR/# )r   r   r   rG   r'   rß   r   r   r   )r   s   "r   r   r     s*   € ÷ #ñ #˜Jð #°[ð #ÐZ^ð #Ðkoñ #r   c               óÄ  € \        V4      '       g   R# \        V 4      p \        V4       F"  p\        V4      '       g   K  \	        V R24      h	  \        V4      p\        W5R7      pT'       dœ   T'       d”   T;'       g     \        P                  P                  T4      p. p	\        Y94       U
Uu. uF  w  r«Y£8w  g   K  Y«3NK  	  pp
p\        Y8YÉ4      w   rÞT'       d+   \        P                   ! RT\#        T4      .T^ ,          O5!   R#   \         d   p\        Y74        Rp?R# Rp?i\         d#   p\        P                  RY7T4        Rp?R# Rp?ii ; iu upp
i )ar  Give the token's user an inheriting full-control ACE on *path* when this process is elevated.

True when a grant was attempted and did not fail; False off Windows, when not elevated, when
*path* is a junction/symlink/reparse point (refused, one warning), or when the grant failed
(logged; the directory itself still exists). *elevated* defaults to the probed host fact.

With *repair_children* (an existing dir an earlier elevated run may have filled), a write that
does not propagate is followed by the same grant on everything already below *path*, never
through a link; children are best-effort and never change the result.Fr^   )rZ   Nz¹Could not grant the current user access to %s created while elevated (%s); a non-elevated Hermes may not be able to use it. To repair, from an elevated prompt run: icacls "%s" /reset /tzAGranting the existing entries under %s failed for %d, e.g. %s: %sT)rÞ   r   rÍ   r$   r   rÔ   r¾   rÛ   Ú	ExceptionrÙ   rÚ   r   r   rÒ   r®   Ú_grant_entriesÚdebugrÇ   )r   rG   rß   ÚtargetÚ	componentrZ   Úchildren_owedrÖ   ÚrealÚskippedÚpÚdÚentriesr�   Úfaileds   &$$            r   Úgrant_user_accessrî     s3  € ô �X×ÒÙÜ�$‹Z€FðÜ/°Ö7ˆIÜ 	×*Ô*Ü)¨Y¨KÐ7eÐ*fÓgÐgñ 8ô # 6Ó*ˆÜ˜vÔ9ˆ÷ Ÿ=Ø×3Ð3œ2Ÿ7™7×+Ñ+¨FÓ3ˆØ ˆÜ&FÀvÔ&WÔgÑ&W™d˜aÐ[\Ñ[f”6�A“6Ñ&WˆÑgÜ% f°GÓE‰ˆˆ1ßÜ�IŠIÐYØœc &›kð7Ø,2°1­Iô7áøô! ô Ü�fÔ"ÝûÜô Ü�‰ð ,à-3¸&ô	Bõ ûð	üó hs;    D Á'D Â1
EÃ EÄEÄD)Ä)EÄ6EÄ7EÅEc               ó$   € V ^8„  d   QhRRRRRR/# )r   Úrootr   ré   rÀ   r   zIterator[tuple[Path, bool]]r   )r   s   "r   r   r   9  s"   € ÷ #ñ #¨:ð #À
ð #ÐOjñ #r   c              #  óh  "  € \        V 4      p \        V 4      '       d   VP                  V 4       R# V .pV'       dÊ   VP                  4       pVR3x €  \        P
                  ! V4      ;_uu_ 4       pV Uu. uF  p\        VP                  4      NK  	  ppRRR4       \        X4       FV  p\        T4      '       d   TP                  T4       K'  TP                  4       '       d   TP                  T4       KP  TR3x € KX  	  KÑ  R# u upi   + '       g   i     L~; i  \         d     Kù  i ; i5i)aR  Yield ``(path, is_dir)`` for *root* and everything under it, never entering or yielding a
junction, symlink or other reparse point (each one is appended to *skipped* instead).

``os.walk`` is not enough: it follows directory junctions (``os.path.islink`` is False for one).
Unreadable directories are skipped silently, like ``icacls /C``.NTF)
r   r$   ÚappendÚpopr   Úscandirr   r   ÚsortedrY   )rð   ré   ÚstackÚ	directoryrì   r±   ÚchildrenÚchilds   &&      r   r®   r®   9  sî   é € ô �‹:€DÜ˜×ÒØ�‰�tÔÙØˆF€Eß
Ø—I‘I“Kˆ	Ø˜ˆoÒð	Ü—’˜I×&Ô&¨'Ù:AÓB¹'°œD §¡Ö,¹'�ÐB÷ 'ô ˜HÖ%ˆEÜ ×&Ò&Ø—‘˜uÖ%Ø—‘—’Ø—‘˜UÖ#à˜U�lÔ"ó &ñ ùò
 C÷ '×&ûäô 	Úð	üsk   ‚:D2½D2ÁD  Á2DÁ7DÂDÂD  ÂAD2Ã'!D2ÄDÄD	ÄD  ÄD2ÄD  Ä D/Ä+D2Ä.D/Ä/D2Úpin_rootc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   rð   r   rú   r   rG   r'   r   ú.tuple[int, list[Path], list[tuple[Path, str]]]r   )r   s   "r   r   r   U  s*   € ÷ $ñ $ ð $¸$ð $Ø%0ð$Ø<jñ$r   c               ó>  € \        V4      '       g   ^ . . 3# \        V'       d    \        P                  P	                  V 4      M\        P                  P                  V 4      4      p . p\        \        W4      4      p\        V \        P                  P	                  V 4      WC4      w  rSpV'       dk   \        P                  R\        V4      \        V4      ^8X  d   RMRT RP                  R VR,           4       4      \        V4      ^8”  d   RMR,           4       WSV3# )	a­  Recursive :func:`grant_user_access` (the elevated post-update repair): grant the token's user
full control on *root* and every directory and file under it, inheriting on directories.

With *pin_root*, *root* is the operator's own choice (``HERMES_HOME``) and may itself be a link:
it is resolved once and the walk runs below the real directory. A link BELOW it is still never
entered or granted through.

Returns ``(granted, skipped, failed)``: *skipped* are the reparse points (and hard-linked files)
that were neither granted nor entered, *failed* the ``(path, reason)`` grants that raised. A
no-op ``(0, [], [])`` off Windows or when not elevated (*elevated* defaults to the probe).zeElevated ACL repair skipped %d junction/symlink/hard-link entr%s under %s (never granted through): %sÚyÚiesz, c              3  ó8   "  € T F  p\        V4      x € K  	  R # 5i©N)rR   )Ú.0rê   s   & r   Ú	<genexpr>Ú)grant_tree_user_access.<locals>.<genexpr>j  s   é € Ð:©k¨œc !Ÿf˜f«kùs   ‚:Né   Nz ...Ú )rÞ   r   r   r   rÒ   rÄ   Úlistr®   rã   rÙ   rÚ   rÇ   rÑ   )rð   rú   rG   ré   rì   Úgrantedrí   s   &$$    r   Úgrant_tree_user_accessr	  U  sã   € ô �X×ÒØ�"�bˆyÐÜ¯(”—‘× Ñ  Ô&¼¿¹¿¹ÈÓ8MÓN€DØ€GÜÔ3°DÓBÓC€GÜ-¨d´B·G±G×4DÑ4DÀTÓ4JÈGÓ]Ñ€G�fßÜ�‰ð +Ü,/°«LÄÀWÃÐQRÔAR¹#ÐX]Ð_cØ—I‘IÑ:¨g°b®kÓ:Ó:ÌÈGËÐWXÔHX¹fÐ^`Õaô	cð ˜VÐ#Ð#r   c          
     ó,   € V ^8„  d   QhRRRRRRRRR	R
/# )r   rð   r   Ú	real_rootrR   rì   zlist[tuple[Path, bool]]ré   rÀ   r   rü   r   )r   s   "r   r   r   n  s2   € ÷ $ñ $˜ð $¨#ð $Ð8Oð $Ø&ð$Ø+Yñ$r   c                ó¬  a € S 0VmpVP                  R V 4       4       \        4       pVS 0,
           F(  pVP                  V 3R lVP                   4       4       K*  	  S V9   d   VP                  S 4       ^ . r‡V Fb  w  rš \	        Yš\
        P                  P                  ! V.V	P                  S 4      P                  O5!  T
;'       d    W•9   R7       V^,          pKd  	  WsV3#   \         d    TP                  T	4        K‰  \         dE   pTP                  T	\        T4      ;'       g    \        T4      P                  34        Rp?KÒ  Rp?ii ; i)a  Grant each walked entry on its own (the pywin32 write does not propagate). The ONE walk
that produced *entries* and *skipped* also decides which dirs hold a link, so the icacls
fallback never re-walks a subtree per directory (it refuses exactly those dirs, as before).c              3  ód   "  € T F&  w  rV'       d   K  \        V4      '       g   K"  Vx € K(  	  R # 5ir  )r¯   )r  rê   rY   s   &  r   r  Ú!_grant_entries.<locals>.<genexpr>t  s!   é € ÐT¡W™	˜´F”!Ô?PÐQR×?S—!’!£Wùs   ‚0“0¦
0c              3  óZ   <"  € T F   qS8X  g   SVP                   9   g   K  Vx € K"  	  R # 5ir  )Úparents)r  rÐ   rð   s   & €r   r  r  w  s&   øé € Ðg©{ VÈ¼nÐPTÐX^×XfÑXfÑPfŸ&š&«{ùs   ƒ+¡
+)r©   N)ÚupdateÚsetr  Úaddr¾   r   r   rÑ   rÅ   rÈ   r   rò   râ   rR   Útyper   )rð   r  rì   ré   r³   Ú
holds_linkÚbadr  rí   r   rY   rÖ   s   f&&&        r   rã   rã   n  s(  ø€ ð
 Ð�WÐ€FØ
‡M�MÑT¡WÓTÔTÜ›E€JØ˜˜�ˆˆØ×ÑÔg¨s¯{ª{ÓgÖgñ àˆw„Ø�‰�tÔØ˜ˆVÛ‰ˆð	Ü�4¤§¡§¢¨iÐ!W¸$×:JÑ:JÈ4Ó:P×:VÑ:VÓ!WØ$*×$AÐ$A¨tÑ/AõCð �q�LŠGñ  ð ˜VÐ#Ð#øô #ô 	!Ø�N‰N˜4× Üô 	BØ�M‰M˜4¤ S£×!?Ð!?¬T°#«Y×-?Ñ-?Ð@×AÒAûð	Bús%   ÂAC&Ã&EÄEÄEÄ9EÅEzset[tuple[Path, int, int]]Ú_grantedc               ó    € V ^8„  d   QhRRRR/# )r   r   r   r   ztuple[Path, int, int] | Noner   )r   s   "r   r   r   �  s   € ÷ &ñ &�Dð &Ð9ñ &r   c                ó„   €  \         P                  ! V 4      pYP                  TP                  3#   \         d     R # i ; ir  )r   r   r   Úst_devÚst_inorº   s   & r   Ú	_identityr  �  s=   € ðÜ�XŠX�d‹^ˆð —‘˜BŸI™IÐ%Ð%øô ô Úðús   ‚0 °?¾?r  Úexist_okc               ó0   € V ^8„  d   QhRRRRRRRRRR	R
R/# )r   r   r   ÚmodeÚintr  r   r  rG   r'   r   r   r   )r   s   "r   r   r   —  s9   € ÷ ñ ˜
ð ¨#ð À4ð Ð[_ð Ø'ðØ37ñr   c          	     ó  € \        V 4      p\        V4      '       g   VP                  WVR7       V# . pTpVP                  4       '       g1   VP                  V8w  d    VP                  V4       VP                  pKF   VP                  WVR7       \        T\        \        T;'       g    T.4      4      \        T4      R7       T#   \         d6    TP                  4       '       d   \        T4      '       g   \        YU.4       h i ; i)a‰  ``Path.mkdir`` (same arguments and defaults) that keeps an elevated Windows creation usable
by the same user unelevated.

Off Windows or unelevated this is ``Path.mkdir`` and nothing else. Elevated, every directory
this call created (missing parents included) is granted the user's inheriting full-control ACE,
and an existing leaf (the repair case) once per directory object per process, except that a
component that is a junction/symlink/reparse point stops the grants with one warning. An
existing leaf without ``exist_ok`` is still granted before the ``FileExistsError``. *elevated*
is the host fact (default: probed); production passes nothing.)r  r  r  )Úfresh)r   rÞ   ÚmkdirÚexistsrÐ   rò   ÚFileExistsErrorrY   r$   Ú_grant_dirsr  Úreversedr  )r   r  r  r  rG   rå   ÚcreatedÚprobes   &&$$$   r   Úmake_user_dirr*  —  sØ   € ô �$‹Z€FÜ�X×ÒØ�‰˜$¸(ˆÔCØˆØ€GØ€EØ�l‰l�nŠn §¡°Ô!6Ø�‰�uÔØ—‘ŠðØ�‰˜$¸(ˆÔCô �œœX g×&9Ð&9°&°Ó:Ó;Ä3ÀwÃ<ÕPØ€Møô ô ð �=‰=�?Š?Ô#3°F×#;Ò#;Ü˜ Ô)Øðús   Á=C Ã DÃ'Dc               ó(   € V ^8„  d   QhRRRRRRRR/# )	r   rå   r   Úto_grantrÀ   r"  z	set[Path]r   rª   r   )r   s   "r   r   r   ¸  s)   € ÷ ñ ˜ð ¨
ð ¸9ð ÐW[ñ r   c                óZ  € \         P                  . \        V 4      OVO4       F,  p\        V4      '       g   K  \	        V\        R4      4        R# 	  V FR  p\        V4      pVe   WB9  d   V\        9   d   K%  \        VRWB9  R7      '       g   K=  \        P                  V4       KT  	  R# )zGrant *to_grant*; a dir in *fresh* (this call created it) is always granted, an existing
one once per verified object identity.z(junction, symlink or other reparse pointNT)rG   rß   )
ÚdictÚfromkeysrÍ   r$   rÛ   r   r  r  rî   r  )rå   r,  r"  ræ   r÷   Úkeys   &&&   r   r&  r&  ¸  s�   € ô
 —]‘]Ð#OÔ%;¸FÓ%CÐ#OÀhÐ#OÖPˆ	Ü˜I×&Ô&Ü˜)Ô%8Ð9cÓ%dÔeÚñ Qó ˆ	Ü˜	Ó"ˆØŠ;˜9Ô1°c¼X´oÙÜ˜Y°ÀyÑG]×^Õ^Ü�L‰L˜Öó r   Úwindowsc               ó$   € V ^8„  d   QhRRRRRR/# )r   r   r   r1  r'   r   rR   r   )r   s   "r   r   r   É  s!   € ÷ 6ñ 6˜jð 6°kð 6ÈSñ 6r   c               ó^   € Vf   \         P                  R8X  d   MV'       d   RV  R2# RV  R2# )z‹The one-line repair for a directory this process may not use (owner from an elevated run).
*windows* is the host fact (default: this host).Úntz%from an elevated prompt run: icacls "z" /reset /tz run: sudo chown -R "$(id -un)" "rž   )r   Úname)r   r1  s   &$r   Úunusable_dir_remedyr6  É  s3   € ð $šOŒ�‰�4�·Ø6°t°f¸KÐHÐHØ-¨d¨V°1Ð5Ð5r   r  )NN)TNN)iÿ  )9Ú__conditional_annotations__r   Ú
__future__r   Ú	functoolsÚloggingr   r   r¡   rN   Úcollections.abcr   Úpathlibr   Ú	getLoggerr   rÙ   r=   Ú_TOKEN_USERr?   rq   r"   rg   rh   ri   rj   rk   rl   r   r   r$   r<   rI   Ú	lru_cacherP   rW   r’   r§   rµ   r¯   r¾   rÍ   rÔ   rÛ   rÞ   rî   r®   r	  rã   r  r  Ú__annotations__r  r*  Ú	frozensetr&  r6  )r7  s   @r   Ú<module>rB     s�  øðôõ6 #ã Û Û 	Û Û Û 
Ý $Ý à×Ò˜Ó!€à€Ø€ØÐ àÐ Ø$Ð Ø€Ø€
Ø€Ø€Ø(Ð Ø *Ð ôa˜'ô aõ
1ð &,Ð "÷#(ðL ×Ò˜QÔô%ó  ð%õ(÷-ð` ×Ò˜QÔôó  ð÷+õ<<÷õ"
QõfõUõCð#À4ð #Ðaf÷ #õL#ð8$Àð $Ø37÷$õ2$ñ< (+£u€Ð
$Ó ,õ&ñÈ%ð Ðbgð Ø*.÷ð ñB HQÃ{÷ ð"6ÀD÷ 6ñ 6r   