Ѡg ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddlm Z m Z ddl mZddlmZmZmZmZmZmZddlZddlZddlmZddlmZmZmZmZdZdZ d Z!d Z"d Z#d Z$dRd Z%dZ&dZ'dZ(dZ) dSdZ*dZ+dee,fdZ-dZ.de/fdZ0dee,fdZ1eddee,fdZ2de3fdZ4de,de/fd Z5de/fd!Z6de/fd"Z7de/fd#Z8d$Z9d%e,de/fd&Z:d'Z;d(Zd+Z?d,e,de/fd-Z@d.e3de/fd/ZAd0edee/fd1ZBde3fd2ZCd3e,dee/e/ffd4ZDd3e,dee/e/ffd5ZEd3e,dee/e/ffd6ZFd7e,d8e/de/fd9ZGd:eejejHfde,fd;ZI dRde,d?e3dee3e,ff d@ZJde,fdAZKde/fdBZLdCe,dee3fdDZMdCe,fdEZNdCe,fdFZOdGZPdHe,de,fdIZQde/fdJZRdKe,de,fdLZSdeee,ee,ffdMZTde/fdNZUde/fdOZVdPZWdQZXdS)TN) ConfigParserError) lru_cache)AnyAnyStrDictOptionalTupleUnion)etree)ExternalProgramFailed check_command exec_utility run_commandz/etc/sysconfig/rhn/systemid)MonTueWedThuFriSatSunz /opt/cloudlinux/litespeed_statusz/opt/cloudlinux/nginx_statusz/opt/apache2nginx/statecd}tj|r& tj|}n#t$rYnwxYw||kr= tj|n#t$rYnwxYwtj||dSdS)a Create symlink link_path -> link_value if it does not exist or points to different location :param link_value: path that symlink should point to (symlink value) :type link_value: str :param link_path: path where to create symlink :type link_path: str N)ospathislinkreadlinkOSErrorunlinksymlink) link_value link_pathlink_tos /builddir/build/BUILDROOT/alt-python27-cllib-3.4.22-1.el8.cloudlinux.x86_64/opt/cloudlinux/venv/lib/python3.11/site-packages/clcommon/utils.pycreate_symlinkr$1sG w~~i   k),,GG    D W  Ii     D  :y))))) s!8 AAA$$ A10A1cg}tj|r?t|dd|5}|}dddn #1swxYwY|S)a Read file and return file's lines errors param may be passed to define how handle unicode errors, errors=None is default value of open() :param path: path to file :param unicode_errors_handle: how to handle unicode errors :return: list of file's lines rutf-8)encodingerrorsN)rrisfileopen readlines)runicode_errors_handlecontentfs r#get_file_linesr0HsG w~~d$ $g6K L L L $PQkkmmG $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ NsAAAct||d5}||ddddS#1swxYwYdS)z Write lines to file :param content: list of lines for writing to file :param path: path to file :param mode: open mode :return: None r'r(N)r+ writelines)rr.moder/s r#write_file_linesr5Ys dD7 + + +q Ws 6::ct|}fd|D}t|t|k}t||d|S)a Delete line from file. Return True when line(s) have been deleted, False otherwise (specified line is not found) :param path: path to file :type path: string :param line: line to delete without EOL (' ') :type line: string :rtype bool cFg|]}|dk|S) )rstrip).0itemlines r# z)delete_line_from_file..ps/OOOtTT[[=N=N5N5Nd5N5N5Nw+)r0lenr5)rr< file_linesout_file_linesfounds ` r#delete_line_from_filerDfsZ %%JOOOOzOOON  OOs>22 2ET>4000 Lr>cLtj}|dkr tjdj}|tjvstj|krdSn#t$rYnwxYwtdtj tj ddSdS)zH Check whether current user is effectively root and exit if not r clsupergidNz'Error: root privileges required. Abort.)file) rgeteuidgrpgetgrnamgr_gid getgroupsgetegidKeyErrorprintsysstderrexit)euidrLs r#is_root_or_exitrUws :<r#is_ea4rXs 7>>2 3 33r>FTc#pK|t|}d}|s1|ds|sd|}tj|}nd}|D]`}|r6|r||dkr|}n/||r|}n||r|}|r||Vn|nd}a||VdSdS)a Grep pattern in file :param multiple_search: if True - search all match, False - search first match :param pattern: pattern for search :param path: path to file :param data_from_file: read data from file for parsing :param fixed_string: if True - search only fixed string, False - search by regexp :param match_any_position: if True - search any match position, False - search only from string begin :return: Generator with matched strings N^rH)r0 startswithrecompilefindsearch) patternr fixed_stringmatch_any_positionmultiple_searchdata_from_fileresult pattern_compr<s r#greprgs*'-- F !!#&& $/A $#'mmGz'**      ! dii&8&8B&>&>)) ""4((   v1LLLL   E  r>c4tjtS)z- :rtype: lxml.etree._ElementTree obj )r parseRHN_SYSTEMID_FILErWr>r#_parse_systemid_filerks ;( ) ))r>returnc( t}|dD]B}|dj|kr"|ddjcSCn)#tt t tjf$rYdSwxYwdS)z` find a member in xml by name and return value :type name: str :rtype: str|None membernamevaluerN) rkiterr^textIOError IndexErrorrOr ParseError)rorhn_systemid_xmlrns r#get_rhn_systemid_valuerws /11&++H55 4 4F{{6""'4//{{7++A.33330 4 Z5+; <tt 4sA$A)'A))"BBcdd|dd}tj|s|S tjd|dgddd }tjd |d gddd }d|d <||d <n/#tjtf$r}d|d|d|d <Yd}~nd}~wwxYw|S)aZ This function is written for detect file system in which file is stored on. E.g., the file can be stored in NFS and this can affect the normal operation of the file. We want to receive information about FS in emergency situations during reading or writing :param file_path: path to file, for which we want to detect file system :return: dict, which contains two keys: key 'success' can be equals to False if we got error or True if we got normal result key 'details' can contais error string if key 'success' is False or result if key 'success' is True FzFile "z" isn't exists)successdetailszdf z | tail -n 1 | awk '{print $NF}'T /bin/bashshell executablerrzmount | grep "on z type"ryrzz#We can't get file system for file "z". Exception ""N)rrexists subprocess check_outputstripCalledProcessErrorr) file_pathre mount_pointdataerrs r#*get_file_system_in_which_file_is_stored_onrs<6I666F 7>>) $ $ c - @9 @ @ @ A"    %''  & 4 4 4 4 5"    %'' !y y  )7 3cccb9bb\_bbbyc MsA(BC+ B<<Cctdd} |d|dd}n#t$rd}YnwxYw|S)z Checks if testing is enabled in /etc/yum.repos.d/cloudlinux.repo config :return: bool value if testing enabled or not NF) interpolationstrictz /etc/yum.repos.d/cloudlinux.repozcloudlinux-updates-testingenabled)rread getbooleanr)parserress r#is_testing_enabled_reporso U ; ; ;F 6777 )maxsizec tdg}n#tjtf$rYdSwxYw|dkr|dddSdS) a Returns virtualization type on current system. It is reachable via virt-what utility. E.g.: 'kvm', 'bhyve', 'openvz', 'qemu' All acceptable outputs are listed here: https://people.redhat.com/~rjones/virt-what/virt-what.txt Output will be returned with at least two rows Sample: > kvm > Furthermore, there is a possibility for multiple text rows Sample: > xen > xen-domU That's why, the result will be taken from a first row. If the output is empty, and there were no errors, the machine is either non-virtual, or virt-what tool isn't familiar with it's hypervisor. But the list of supported hypervisors and containers covers all popular VMs types. :return: virt_type - Optional[AnyStr] - appropriate virtualization type string, - 'physical' if there is no virtualization, - None if there was an error z/usr/sbin/virt-whatNr8r)maxsplitrphysical)rrrrFileNotFoundErrorr)virt_what_outputs r# get_virt_typerRs8&(='>??EEGG  )+< =tt2%%dQ%77::zs "%??pidcT tj|ddS#t$rYdSwxYw)z Checks for a process existence by os.kill command If os.kill will be used as os.kill(pid, 0), it will just check for a presence of such PID And if such pid can't be reached with kill method, there will be raised OSError rTF)rkillr)rs r# check_pidrysA Qt uus  '' pid_file_pathcRtj|rt|d5} t t |cdddS#t$rYnwxYw dddn #1swxYwYdS)zCheck if process running using pid file Arguments: pid_file_path: path to the pid file of service Returns: bool: True or False r'r2NF) rrr*r+rintrr ValueError)rr/s r#is_process_runningrs w~~m$$ -' 2 2 2 a  QVVXX^^%5%5!6!677                             5s.B?A?? B  B B  BB #B cRtjdkrd}t|Stjt sdSt t d5}|}dddn #1swxYwY|dkS)a  Detects that server works under Litespeed. Note: be careful when modifying this method. It is used in X-Ray, ask @dkavchuk or someone else from C-Projects team for details. return: True - LS working; False - LS not running (stopped or absent) rz/tmp/lshttpd/lshttpd.pidFr'r2N0) rrIrrrLITESPEED_STATUS_FOR_USERSr+rrrr/statuss r#is_litespeed_runningrs z||q2 !-000w~~899 !507CCC *q)) * * * * * * * * * * * * * * *S= %'BBBcRtjdkrd}t|Stjt sdSt t d5}|}dddn #1swxYwY|dkS)a Detects that server works under nginx. Note: be careful when modifying this method. It is used in X-Ray, ask @dkavchuk or someone else from C-Projects team for details. return: True - nginx working; False - nginx not running (stopped or absent) rz/run/nginx.pidFr'r2Nr) rrIrrrNGINX_STATUS_FOR_USERSr+rrrs r#is_nginx_runningrs z||q( !-000w~~455 !5,w??? *1)) * * * * * * * * * * * * * * *S= rctjtsdSt td5}|}dddn #1swxYwY|dkS)a# Detects that server works under apache2nginx. Note: be careful when modifying this method. It is used in X-Ray, ask @dkavchuk or someone else from C-Projects team for details. return: True - apache2nginx working; False - apache2nginx not running (stopped or absent) Fr'r2Non)rrrAPACHE2NGINX_STATEr+rr)r/rs r#is_apache2nginx_runningrs 7>>, - -u $w 7 7 7 &1VVXX^^%%F & & & & & & & & & & & & & & &~s'A00A47A4c&trdSdS)zU Return proper passenger package according to apache version :rtype: str zapache24-passengerzalt-mod-passenger)rXrWr>r#get_passenger_package_namers xx$ $#  r> package_namec trtddd|g}d|vrdSntdd|gn#t$rYdSwxYwdS) z Checks that the given package is installed on the server. :param package_name: Package name to check. :return: True if package is installed, False otherwise. z dpkg-queryz-Wz-f='${Status} 'zinstall ok installedFrpmz-qT)rrr )r pkg_statuss r#is_package_installedrs  ;; 5%lD:Ll%[\\J&Z77u8 l3 4 4 4 uu 4s%== A  A cVtrtStS)z Pick the package manager to check depending on the OS. If we're not on Ubuntu, assume a CL variant with RPM. :return: Error string, if any, None otherwise. )rget_apt_db_errorsget_rpm_db_errorsrWr>r#get_package_db_errorsrs({{# """ """r>cb tjddgdtjtjd5}|\}}|jdkrd|d|d cd d d S d d d n #1swxYwYn-#t t f$r}t|cYd }~Sd }~wwxYwd S) z Check the dpkg DB as described in https://man7.org/linux/man-pages/man1/dpkg.1.html See `--audit`. :return: Error string, if any, None otherwise. dpkgz--auditFT)r}stdoutrRrrrzdpkg audit error: r8rNrPopenPIPE communicate returncoderrsstr)procstd_outstd_errrs r#rrsM   Y ??    B #//11 GW!##AGAAwAAA B B B B B B B B$ B B B B B B B B B B B B B B B W 3xx 4sF/B+A6 B* B6A::B=A:>BB,B'!B,'B,chd} tjgdtjtjd5}|\}}|jdkrd|d|d|cd d d S d d d n #1swxYwYn-#t t f$r}t|cYd }~Sd }~wwxYwd S) zx Check RPM DB consistency. :return: None - No RPM DB errors string_message - Error description zjhttps://cloudlinux.zendesk.com/hc/en-us/articles/115004075294-Fix-rpmdb-Thread-died-in-Berkeley-DB-library)z/bin/rpmz--dbpathz /var/lib/rpmz --verifydbT)rrRrrrzRPM DB check error: r8z . See doc: Nr)doc_linkrrrrs r#rr!s[ D     B B B??    Y #//11 GW!##XgXXXXhXX Y Y Y Y Y Y Y Y$ Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y W 3xx 4sF.B-A9 B- B9A==BA=BB/B*$B/*B/cztjt_tjt_dS)a( Upon process exit, Sentry sometimes prints: Sentry is attempting to send 1 pending error messages Waiting up to 10 seconds Press Ctrl-C to quit This causes broken JSON in output. See also this issue: https://github.com/getsentry/raven-python/issues/904 N)ioStringIOrQrrRrWr>r#!silence_stdout_until_process_exitr=s"CJCJJJr>cd|dzz }tj|5tj||ddddS#1swxYwYdS)a Create directories with desired permissions Changed in version 3.7: The mode argument no longer affects the file permission bits of newly-created intermediate-level directories. Because it we use umask while creating dirs :param mod: desired permissions iN)secureio set_umaskrmakedirs)rmod inverted_mods r# mod_makedirsrLsC%K(L  L ) ) D#sAAAusernamecT tj|n#t$rYdSwxYwdS)z, Check user existence in the system FT)pwdgetpwnamrO)rs r#is_user_presentr[sA X uu 4  %%uidcT tj|n#t$rYdSwxYwdS)z+ Check uid existence in the system FT)rgetpwuidrO)rs r#is_uid_presentrfsA S uu 4rrc tj|j}n#ttt f$rYdSwxYwt j|}|S)z+ Check that file by path is socket N)rlstatst_moderrsrstatS_ISSOCK)rr4 is_sockets r#is_socket_filerqsWx~~% w 0tt d##I s 77ctjdddd}|d}|dkrdnt |}|S)z? Get number of system run level by command `runlevel`. z/sbin/runlevelTr{r|rS)rrrrr)outputrelevels r#get_system_runlevelr ~sc  $  F\\^^ ! ! # #A &F3AACKKE Lr> service_namect} tjd|ddd}n#tjtf$rYdSwxYw|}|ddD]V}|d}|dd k}t|d }||krd|fcSWd S) z Returns state of a service (present and enabled) for init.d system. Returns False, False if a service doesn't exist Returns True, False if a service exists and it's not enabled Returns True, True if a service exists and it's enabled zLANG=C /sbin/chkconfig --list Tr{r|FFrN:rrTF)r rrrrrrr)r runlevelr  output_list state_info state_list is_activestate_runlevels r##_get_service_state_on_init_d_systemrs#$$H ( ;\ ; ;"      )+< >||,,..&&((K!!""o## %%''--c22 qMT) Z]++ ~ % %? " " " & ;s,AAc tjd|ddddS#tjtf$r tjdd|gtjtj 5}||jd vr d d d Yd S d d d Yd S#1swxYwYYd S#tjtf$rYYd SwxYwwxYw)z Returns state of service (present and enabled) for systemd system Returns False, False if a service doesn't exist Returns True, False if a service exists and it's not enabled Returns True, True if a service exists and it's enabled z/usr/bin/systemctl is-enabled z &> /dev/nullTr{)r}r~)TTz/usr/bin/systemctlr)rRr)rNrr)r check_callrrrDEVNULLrr)r rs r#$_get_service_state_on_systemd_systemrs  I\ H H H"     z  )+< >    !(  ")!) (  """?f,,& ( ( ( ( ( ( ( ( ( (! ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ("-/@B   <<< ' sVC .B.$B  B.B  B. B$ $B.'B$ (B..C C C  C ct}|rd|vrt|\}}nt|\}}||fS)zc Returns state of service (present and enabled) :param service_name: name of a service r)r )rrr)r cl_ver is_present is_enableds r#service_is_enabled_and_presentr!si   F  %6//!D%" " "  JJ"F%" " "  J z !!r>process_file_path strict_matchcVtj|std|dt jdgD]`} |s||vs-|r.|d|krdSL#tj$rY]wxYwdS)a Check that a file in path is running. You can get false-postive if parameter `strict_match` == False, process is not running, but someone on server open file by path `process_file_path` in an editor :param process_file_path: path to a file which is run :param strict_match: we use parameter `process_file_path` as full cmd line with args for comparing if `strict_match` == True. :return: True if it's running, False - is not, zProcess file in path "" does not existcmdline TF) rrrrpsutil process_iterr&join NoSuchProcess)r"r#processs r#process_is_runningr-s 7>>+ , ,  H%6 H H H   & {33     %):):::;&'//2C2C)D)DDDtt#    H  5s ABB&%B&dtct|tjtjfstdt |t |S)z Getting string representation of weekday from datetime.datetime or datetime.date. Returns shortened version of weekday from WEEK_DAYS. zFRequire object of type datetime.datetime or datetime.date, but passed ) isinstancedatetimedate TypeErrortype WEEK_DAYSweekday)r.s r# get_weekdayr7 s\ rH-x}= > >  _UYZ\U]U] _ _    RZZ\\ ""r> config_pathapache_module_name param_namedefaultcd|d}g}t|}d}|D]@}|}||krd}|r|dkrn|r||Att dd|} fd| D} | s||d fS| s | d d |d |fS| d d} t | d } | d fS)a Helper to parse httpd config for details about mpm module used :param config_path: path for configuration file with modules :param apache_module_name: expected mpm module. Can be `event`, `worker`, `prefork` :param param_name: name of parameter to find :param default: default value for parameter, if there won't be record :return: tuple with param value and text result of operation Example of config file content: ................. MaxRequestWorkers 450 -- ................. MaxRequestWorkers 300 -- ................. MaxRequestWorkers 2048 zFTz )rcrardcg|]=}|)|>SrW)rr[)r: directiver:s r#r=z/find_module_param_in_config..KsQ  ??   ' ' 3 3r>NOKrz.MaxRequestWorkers directive not found for mpm_z_module module in rHr')r0rappendlistrgrr) r8r9r:r;if_module_line section_lines mpm_linesis_section_foundr<grep_result_listmrw_listpartsmax_request_workerss ` r#find_module_param_in_configrJsu>C&8BBBNM{++I ' 'zz|| > ! !#     5 5 E  '   & & &  (    )H   +}  '/  G% G G9D G G   RL  s # #EeBi..  $$r>c^d}tj|sdS t|dd5}|}dddn #1swxYwY|dd\}}n#tttf$rYdSwxYw|S) zp Return kmodlve module's version. Content of '/sys/module/kmodlve/version' looks like '2.0-30.el8'. z/sys/module/kmodlve/versionrr&r'r2Nrr) rrr*r+rrrsplitrrsr)kmodlve_module_file_pathr/modulemodule_version_s r#get_kmodlve_module_versionrQcs = 7>>2 3 3r *C' B B B &aVVXX^^%%F & & & & & & & & & & & & & & &"MM#q11 Wj )rr s4B'A* B*A..B1A.2BB*)B*crddi}t}||vrdS||}t}||krdSdS)a Find out if system has version of the kernel (according to kmodlve module's version) where fs.proc_can_see_other_uid and hidepid options are synchronized. They are only synchronized if kmodlve module's version is equal to version in synced_kmodlve_versions. rz2.0-30FT)rrQ)synced_kmodlve_versionsrsynced_versioncurrent_versions r#)proc_can_see_other_uid_and_hidepid_syncedrVtsU %h/!!J000u,Z8N022O.((t 5r> pid_filenamec, t|dd5}t|}t j|ddddn #1swxYwYn#t ttf$rd}YnwxYw|S)z Detrmines working daemon process pid :param pid_filename: PID filename :return: PID from file or None if error (file not found, etc) r&r'r2rN) r+rrrrrrrsr)rWpfrs r#get_process_pidrZs  ,g 6 6 6 "bggiioo''((C GCOOO                Wj ) Js5A5A A) A5)A--A50A-1A55BBct|dd5}|tjddddS#1swxYwYdS)z Writes pid file r?r'r2N)r+writergetpid)rWrYs r#write_pid_filer^s lD7 3 3 3#r BIKK!"""##################s(AA A c` tj|dS#ttf$rYdSwxYw)z Remove PID file N)rremoverrs)rWs r#remove_pid_filerasD  , W      s --cfd}|S)a Set user's real uid and gid to specified ones. Checking equality of real and effective uids is needed because this function may be used by root with effective uid dropped to user's uid. In that case it is needed to set effective uid back to 0 first. ctj}tj}||krtj|tjtjdSN)rgetuidrIseteuidsetgidsetuid)real_uideff_uidgidrs r#funczdemote..funcsR9;;*,, w   Jx  # #r>rW)rrkrls`` r#demoterms) Kr>dirpathcLtj|std|ddd|g}t |} |dd}|dd}n%#t $r}td ||d }~wwxYw|S) zK Get mount point for dirpath directory from output of `df` utility z Directory "r%z/bin/dfz-hr8rr'rHz)Utility "df" returned unexpected output: N)rrisdirrrrrt)rn fs_info_cmdfs_infomountpoint_info mounted_ones r#get_mount_pointrvs 7== ! !?=G===>>>dG,K+&&G U!----a0$**3//3 UUUL7LLMMSTTU s6A?? B! BB!c d}tj|stdkSt |d5}t |}|dkcdddS#1swxYwYdS)z Detect if 'may_detach_mounts' kernel option is enabled. More info on the option: https://cloudlinux.atlassian.net/browse/KMODLVE-512 z/proc/sys/fs/may_detach_mountsrr'r2rN)rrr*rr+rr)may_detach_mounts_filer/vals r#is_may_detach_mounts_enabledrzs > 7>>0 1 1)5(( $w 7 7 71!&&((mmaxs'A99A=A=devicec^tj}|D]}|j|kr |jcSdS)z; Return the file system type for the given device. r)r(disk_partitionsr{fstype)r{ partitions partitions r#get_filesystem_typersH'))J$$  v % %# # # # & 2r>c d}d}tj}t|d5}|d|d|zdddn #1swxYwY||dd }||d d }||fS#tttj f$rYnwxYwd S) zM Detect system name and version :return: tuple (os_name, os_ver) z/etc/os-releasetopr'r2[z] NNAMEr VERSION_ID)NN) configparserrr+ read_stringrgetrrrsr)os_release_filename section_nameconfigstreamos_nameos_vers r#rrs>  / *,, % 8 8 8 FF   4<444v{{}}D E E E F F F F F F F F F F F F F F F**\62288==L,77==cBB Wl0 1     :s5(C/A% C%A))C,A)-ACC%$C%c0t\}}|dkS)z Determines whether this system is Ubuntu. Reads /etc/os-release file to do so. :return: bool flag is_ubuntu Ubuntu)r)rrPs r#rr s !!JGQ h r>cd}tjdr1tjdddtjtjdk}|S)z] Determines if secure boot is turned on :return: bool flag is_secureboot_enabled Fz/sys/firmware/efiz!mokutil --sb-state | grep enabledTr{)r}r~rrRr)rrrrcallr)rs r#is_secureboot_enabledrs] G w~~)**]/"E(,)3);JDVXXX[\] Nr>c| tjtjdS#t$rYdSwxYw)z' Get username of current user. rN)rrrre ExceptionrWr>r# get_usernamer#sD|BIKK((++ tts *- ;;c&trdSdS)z- It differs on Ubuntu and CloudLinux z%usr/lib/x86_64-linux-gnu/php/modules/zusr/lib64/php/modules/)rrWr>r#get_modules_dir_for_alt_phpr,s{{766 # #r>rd)NFTFN)Yrr1rJrrrrr\rrrQrr functoolsrtypingrrrr r r r(rlxmlr clcommon.utils_cmdr rrrrjr5rrrr$r0r5rDrUrXrgrkrrwrboolrrrrrrrrrrrrrrrrrrrr rrr!r-r2r7rJrQrVrZr^rarmrvrzrrrrrrrWr>r#rs   ,,,,,,,,<<<<<<<<<<<<<<<<  2 = ?7.***."   "$444 ////d*** HSM     %%%P     > >>>>B 4#x}####L3"cd"!d!!!!(!$!!!!(        st0 # # #48      cd  HTN    S&ceD$J>OD$ s$ uT4Z?P$ $ $ $ N""tTz9J"""""#Tt< #E(+X]:; # # # # #  K%K%K%K%K% K%  38_ K%K%K%K%\C"4& # (3-     ##### #    ,SS, d     hsmXc]:;4 4     t    $$$$$r>