original in en Erdal Mutlu
en to tr Erdal Mutlu
Erdal, LF'nin Türk editörlerinden birisidir. Şu anda Sistem yöneticisi olarak Linotype Library'de çalışmaktadır. Üniversite yıllarından beri bir Linux hayranıdır ve bu ortamda çalışmaktan ve uygulama geliştirmekten hoşlanmaktadır.
Bu yazıyı takip edebilmek için kabuk programlama ile ilgili temel bilgiye sahip olmanız gerekmektedir. Kabuk programlama ile ilgili daha fazla bilgi LF'nin Katja ve Guido Socher tarafından yazılmış Kabuk programlama yazısını okuyabilirsiniz. Ayrıca, ssh-keygen, ssh-add, scp, sftp gibi ssh araçları hakkında da bilgi sahibi olmanız gerekmektedir. SSH protokolünün Linux için serbest yazılım çerçevesinde geliştirilmiş araçlarını OpenSSH'dan bulubilirisniz. Ayrıca, bunların man sayfaları da vardır.
scp /path/to/the/file/file1 user@remote_host:/remotedir/newfile
Bu örnekte yerel bilgisayardaki file1 dosyası remote_host (Bu IP adresi de olabilir.) adındaki bilgisayarın /remotedir dizinine newfile yeni ismiyle kopyalanmaktadır. Eğer, 'user' olarak girdiğiniz geçişsözcüğü doğru ve kopyalamak için gerekli haklara sahip iseniz, kopyalama işlemi gerçekleştirilecektir. İsterseniz hedef dosya ismin vermeyebilirsiniz. Bu durumda dosya aynı isimle kopyalanacaktır. Kısacası, kopyalama sırasında dosya adını değiştirmek mümkün olmaktadır.scp user@remote_host:/remotedir/file /path/to/local/folder/newfile
scp komutunun çok kullanışlı bir özelliği daha var. Bir dizini tüm içeriği ile birlikte, yani tüm dosyaları ve dizinleri ile birlikte kopyalamak için '-r' seçeneği kullanılmaktadır.scp -r user@remote_host:/remotedir .
Yukarıdaki komut, remote_host adlı bilgisayarda bulunan remotedir dizini ve içerdiği tüm dosya ve dizinleri ile birlikte bulunduğumuz dizin altına aynı adla kopyalamaktadır. Not:Burada remote_host adlı bilgisayarda sshd servisinin çalıştığı varsayılmıştır.ssh erdal@helvetica.fonts.de
Yapılandırmanıza göre geçişsözcüğü veya geçişcümlesi girmeniz gerekecektir. Eğer, giridiğiniz bilgiler doğru ise, helvetica.fonts.de bilgisayarına erdal kullanıcısı ile bağlanmış olacaksınız. ssh komutunun gereksinimlerinize göre çeşitli seçenekleri vardır. Bunun için ssh'nın man sayfasına bakabilirsiniz.ssh erdal@helvetica.fonts.de df -H
Komutun kullanımı uaktaki bilgisayara erişim ile öok benzeşmektedir. Tek fark, bilgisayara adından sonra çalıştırılacak komutun (Örneğimizde 'df -H') yazılmasıdır. Komutun çıktısı yerel bilgisayarda görüntülenecektirssh-keygen -b 1024 -t dsa
Komutu çalıştırıldığında özel (private) anahtarı için dosya adı girmeniz gerekecektir. Genel anahtarın saklandığı dosya adını, özel anahtar dosya adına '.pub' eklenmesi ile oluşturulmaktadır. '-b 1024' anahtarda yer alan bit sayısıdır. Eğer, böyle bir değer belirtmezseniz benimsenmiş değer kullanılacaktır. '-t dsa' ile anahtar tipi belirlenmektedir. 1 sürüm numaralı protokol için 'rsa1', 'rsa' veya 'dsa' ise 2 sürüm nolu protokol için olan seçeneklerdir. Size tavsiyen SSH'nın 2 sürümlü protokulünü kullanmanızdır. Ancak, sisteminizde çok eski sunucular varsa, ki bunlar sadece 1 nolu protokolü destekliyorlardır, o zaman '-t rsa1' seçeneği ile bir anahtar çifti yaratmanız gerekecektir. ssh programını kullanırken 1 veya 2 sürüm nolu protokolün kullanılmasını sağlamanız mümkündür. Bunun için sırasıyla '-1' veya '-2' seçeneklerini kullanmanız gerekecektir.Anahtarınızı kullanabilmek için genel (public) olanını bağlanacağınız bilgisayara yüklemeniz gerekecektir. Bunun için genel anahtarın içeriği $HOME/.ssh/authorized_keys veya $HOME/.ssh/authorized_keys2 dosyasına eklenmelidir. İki protokol için olan anahtarlarınızı karıştırmamaya dikkat etmelisiniz. authorized_keys 1 sürüm nolu SSH protokolü, authorized_keys2 ise 2 sürüm nolu SSH protokolü için kullanılamaktadır. Eğer, anahtarlarınızı düzgün bir şekilde yüklediyseniz, o bilgisayara bir sonraki bağlantınızda öncelikle geçişcümlesi girmeniz istenecektir. Yanlış giriş yapmanız durumunda, geçişsözcüğü girmeniz istenebilir. sshd'nin /etc/ssh/sshd_config yapılandırma dosyasında 'PasswordAuthentication no' değişikliğini yaparak o bilgisayara sadece anahtar kimliklendirilmesi kullanılarak giriş yapılmasını sağlayabilirsiniz.
Şu ana kadar herşey tamamdır. Başka bir bilgisayarda uzaktan komut çalıştırma ve oradan kendi bilgisayarımıza veya kendi bilgisayarımızdan uzaktaki bilgisayara güvenli bir şekilde dosya kopyalamanın yollarını öğrendik. Ancak, işleri otomatikleştirmek için geçişsözcüğü veya geçişcümlesi girmememiz gerekir. Yoksa, otomatikleştirmeyi unutun. Bir çözüm, herbir kabuk programı içine geçişsözcüğünü veya geçişcümlesini yazmak olabilir. Daha iyi bir yöntem ise, ssh-agent kullanmaktır. ssh-agent, arka planda çalışan ve özel anahtarları saklayarak sizin yerinize otomatik olarak kullanan bir araçtır. Programı aşağıdaki gibi çalıştırmak mümkündür:
ssh-agent $BASH
Özel anhtarlarınızı eklemek içinssh-add .ssh/id_dsa
veyassh-add .ssh/identity
komutları kullanılabilir. id_dsa DSA tipli özel anahtar, identity ise RSA1 tipli özel anahtardır. Bunlar, ssh-keygen programını kullanırken benimsenmiş değer olarak yaratılan dosya isimleridir. ssh-add anahtarları ssh-agent'a eklemeden önce geçişcümlenizin girilmesini isteyecektir. Eklenmiş anahtarlarların listesini aşağıdaki komutla alabilirsiniz:ssh-add -l
Eğer, anahtarınızı yüklediğiniz bilgisayara tekrar bağlanırsanız, hiçbirşey girmeden bağlantı sağlanmış olur! ssh-agent kimliklendirme işlemini yerine getirmiş olacaktır.
ssh-agent'ı yukarıdaki gibi kullandığınızda, sadece çalıştırdığınız konsoldan kullanmanız mümkündür. Eğer, herhangi bir konsoldan kullanmak istiyorsanız, biraz daha fazla işlem yapmanız gerekecektir. ssh-agent'ı çalıştıran küçük bir kabuk programı yazdım:
#!/bin/sh # # Erdal mutlu # # Starting an ssh-agent for batch jobs usage. agent_info_file=~/.ssh/agent_info if [ -f $agent_info_file ]; then echo "Agent info file : $agent_info_file exists." echo "make sure that no ssh-agent is running and then delete this file." exit 1 fi ssh-agent | head -2 > $agent_info_file chmod 600 $agent_info_file exit 0
Yukarıdaki kabuk programı, kullanıcının ev dizinini altında ssh ile ilgili dosyalarının bulunduğu dizin, ki genelde bu '.ssh/' dır, içindeki agent_info dosyasının varlığını denetlemektedir. Eğer, bu dosya varsa, kullanıcı dosyanın varlığı hakkında uyarılmakta ve ne yapılabileceği hakkında kısa bir mesaj görüntülenmektedir. Eğer, kullanıcı ssh-agent programını çalıştırmadıysa, agent_info dosyasını silinmesi ve kabuk programının tekrar çalıştırılması gerekmektedir. Kabuk programı ssh-agent'ı çalıştırıp, onun çıktısının ilk iki satırını agent_info dosyasına kaydetmektedir. Bu bilgi ssh araçları tarafından daha sonraları kullanılacaktır. En sonunda ise, dosyayı sadece kullanıcının okuyup yazablmesine yönelik dosya hakları chmod komutuyla değiştirilmektedir.
ssh-agent çalıştırdığınız andan itibaren anahtarlarınızı eklemeniz mümkündür. Ancak, daha öncesinde agent_info dosyasındaki bilgileri ssh araçlarının ulaşabilecek duruma getirmeniz gerekmektedir. Bunun için aşağıdaki komutlardan birini kullanabilirsiniz:
source ~/.ssh/agent_info veya . ~/.ssh/agent_info
Şimdi ssh-add ile özel anahtarlarınızı ekleyebilirsiniz. Aşağıdaki satırları .bashrc dosyasına ekleyerek, her yeni konsol açtığınızda agent_info bilgilerine ulaşmış olacaksınız:
if [ -f .ssh/agent_info ]; then . .ssh/agent_info fi
UYARI: ssh-agent ve otomatik yükleme kabuk programını çalıştıracağınız bilgisayarın güvenliğini sağlamak zorundasınız. Yoksa, sizin hesabınıza ulaşabilen herkes, anahtarlarınız ile ulaştığınız tüm hesaplara ulaşabilecektir. Herşeyin bir fiyatı var tabii!
Sistem yönetcisinin bazı işlerini nasıl otomatikleştireceğimizi anlatmanın zamanı geldi artık. Programın sağlayacağı şey, belli sayıdaki komutu belli bilgisayarlarda çalıştırmak ve o bilgisayarlara veya o bilgisayarlardan yerel bilgisayara dosyalar kopyalamaktır. Bu da zaten sistem yöneticisinin sıkça yaptığı işlerdendir. Kabuk programının çıktısı aşağıdadır:
#!/bin/sh # Installing anything using Secure SHELL and SSH agent # Erdal MUTLU # 11.03.2001 ################################################################## # Functions # ################################################################## ### Copy files between hosts copy_files() { if [ $files_file != "files_empty.txt" ];then cat $files_file | grep -v "#" | while read -r line do direction=`echo ${line} | cut -d " " -f 1` file1=`echo ${line} | cut -d " " -f 2` file2=`echo ${line} | cut -d " " -f 3` case ${direction} in "l2r") : ### From localhost to remote host echo "$file1 --> ${host}:${file2}" scp $file1 root@${host}:${file2} ;; "r2l") : ### From remote host to localhost echo "${host}:${file2} --> localhost:${file2}" scp root@${host}:${file1} ${file2} ;; *) echo "Unknown direction of copy : ${direction}" echo "Must be either local or remote." ;; esac done fi } ### Execute commands on remote hosts execute_commands() { if [ $commands_file != "commands_empty.txt" ];then cat $commands_file | grep -v "#" | while read -r line do command_str="${line}" echo "Executing $command_str ..." ssh -x -a root@${host} ${command_str} & wait $! echo "Execute $command_str OK." done fi } ### Wrapper function to execute_commands and copy_files functions doit() { cat $host_file | grep -v "#" | while read -r host do echo "host=$host processing..." case "${mode}" in "1") copy_files execute_commands ;; "2") execute_commands copy_files ;; *) echo "$0 : Unknown mode : ${mode}" ;; esac echo "host=$host ok." echo "------------------------------------------------------------------" done } ################################################################## ### Program starts here ################################################################## if [ $# -ne 4 ]; then echo "Usage : $0 mode host_file files_file commands_file" echo "" echo "mode is 1 or 2 " echo " 1 : first copy files and then execute commands." echo " 2 : first execute commands and then copy files." echo "If the name of files.txt is files_empty.txt then it is not processed." echo "If the name of commands.txt is commands_empty.txt then it is echo "not processed." exit fi mode=$1 host_file=$2 files_file=$3 commands_file=$4 agent_info_file=~/.ssh/agent_info if [ -f $agent_info_file ]; then . $agent_info_file fi if [ ! -f $host_file ]; then echo "Hosts file : $host_file does not exist!" exit 1 fi if [ $files_file != "files_empty.txt" -a ! -f $files_file ]; then echo "Files file : $files_file does not exist!" exit 1 fi if [ $commands_file != "commands_empty.txt" -a ! -f $commands_file ]; then echo "Commands file : $commands_file does not exist!" exit 1 fi #### Do everything there doit
Kabuk programını ainstal.sh (automated installation) adı altında kayıt edelim ve herhangi bir parametre vermeden çalıştırmayı deneyelim. Aşağıdaki mesajı elde ederiz:
./ainstall.sh
Usage : ./ainstall.sh mode host_file files_file commands_file mode is 1 or 2 1 : first copy files and then execute commands. 2 : first execute commands and then copy files. If the name of files.txt is files_empty.txt then it is not processed. If the name of commands.txt is commands_empty.txt then it is not processed. |
Eğer, komut çalıştrımak istemeyiyorsanız, commands_file parametresi yerine commands_empty.txt verebilirsiniz ve eğer, dosya aktarmak istemiyorsanız files_file parametresi yerine de files_empty.txt ismini verebilirsiniz.
Programı satır satır açıklamaya geçmeden önce bir örnek üzerinde
duralım: Diyelim ki sisteminize ikinci bir DNS sunucu eklediğinizde,
bilgisayarların /etc/resolv.conf dosyasına bilgisayarın IP numarasını
eklemeniz gerekecektir. Kolaylık olsun diye tüm bilgisayarlarınızın aynı
resolv.conf dosyasına sahip olduğunu varsayılım. Dolayısıyla, yapmamız
gereken tek şey yeni bir resolv.conf dosyasını her bilgisayara
kopyalamaktır.
İlk önce hangi bilgisayarlara kopyalama yapacağınızı bilmeniz
gerekecektir. İlgili tüm bilgisayarları hosts.txt dosyasına yazalım.
Bu dosyanın biçimi, her satıra bir bilgisayar adı veya IP numarası gelecek
şekilde bir listeden oluşmaktadır. İşte bir örnek dosya:
########################################################################## #### Every line contains one hostname or IP address of a host. Lines that #### begin with or contain # character are ignored. ########################################################################## helvetica.fonts.de optima.fonts.de zaphino vectora #10.10.10.162 10.10.10.106 193.103.125.43 10.53.103.120 |
Örnekten görüldüğü gibi tam bilgisayar adı verebildiğimiz gibi, sadece bilgisayar adı vermek, yani alan kısmı olmadan, mümkündür. Daha sonra hengi dosyaların aktarılacağını belirten bir dosyaya gereksinim olacaktır. İki olasılık vardır:
Aktracağımız dosyaları files_file.txt adında bir dosyaya yazalım. Dosyanın biçimi şu şekildedir: Her satır sadece bir dosyanın aktarımı bilgisini içermektedir. Bilgiler üç sütun halindedir ve bir boşluk veya tab karakteri ile ayrılmaktadır. l2r (local to remote) yerel bilgisayardan uzaktaki bilgisayara ve r2l (remote to local) uzaktaki bilgisayardan yerel bilgisayara olarak iki olası dosya aktarım yolu vardır. Aktarım yönünü belirten kelimeden sonra iki dosya adı gelmektedir. İlk gelen dosya aktarım yönüne göre diğeri üzerine kopyalanmaktadır. Uzaktaki bilgisayar için verilen dosya adı yoltanımı ile verilmelidir, yoksa kopyalama root kullanıcısının ev dizinine olacaktır. İşte bizim files_file.txt :
############################################################################ # The structure of this file is : # - The meaning of the fileds are : is l2r (localhost to remote) and r2l # (remote computer to local). # r2l file1 file2 # means copy file1 from remote (hosts specified in the # hosts.txt file) computer to localhost as file2. # l2r file1 file2 # means copy file1 from localhost to # remote (hosts specified in the hosts.txt file) computer as file2 # file1 and file2 are files on the corrsponding hosts. # # Note: the order of using local and remote specifies the direction # of the copy process. ############################################################################ l2r resolv.conf /etc/resolv.conf |
Görüldüğü gibi dosyanın başında açıklama (İngilizce olarak) yer almaktadır. Bu açıklamayı genellikle her dosyanın başına getiriyorum. Belgelendirme yapmak yerine böyle bir çözüm kullanıyorum. Bu basit ve güzel bir çözümdür. Örneğimizde, resolv.conf dosyasını uzaktaki bilgisayarların /etc dizine aynı isimde kopyalayacağız. Göstermek amacıyla, kopyalamadan sonra dosyanın kullanıcı ve erişim haklarını değiştiren komutlar ekledim. Çalıştırılacak komutlar ayrı bir dosyaya yazılmaktadır. Dosyanın biçimi her satır bir tek komut içerecek şekildedir. Komutların yer aldığı dosyaya commands_file.txt adını verelim:
########################################################################### # The structure of this file is : Every line contains a command to be # executed. Every command is treated seperately. ########################################################################### chown root.root /etc/resolv.conf chmod 644 /etc/resolv.conf cat /etc/resolv.conf |
Komutlar sırayla çalıştırlılmaktadır ve biri bitmeden diğer komuta geçilmemektedir.
Evet, programı çalıştırmak için gerekli olan herşeye sahibiz artık. Belirtilmesi gerek tek şey 'mode' seçeneğidir. Bu, dosya aktarımımı ardından komutlar mı çalıştırılsın, yoksa komutlar çalıştırıldıktan sonra dosyalar mı aktarılsın seçeneklerini belirtmeye ayaramaktadır. İlk önce dosyalar aktarılsın ve ardından komutlar çalıştırılsın seçeneği mode=1 durumuna karşılık gelmektedir.Önce komutlar çalıştırılsın ve ardından dosyalar aktarılsın seçeneği mode=2 durumuna karşılık gelmektedir. Şimdi programı gerekli tüm seçenekler ile birlikte çalıştırabiliriz:
./ainstall.sh 1 hosts.txt files_file.txt commands_file.txt
Küçük bir öneri: files.txt dosya adınının başına files_ (files_resolvconf.txt) ve commands.txt'nin başına commands_ (commands_resolvconf.txt) yazmanızı öneririm.
Programın kendisini açıklama zamanı geldi artık. Program, kendisine verilen parametre sayısanı denetleyerek başlamaktadır ve eğer bu sayı 4 değil ise, kullanım mesajı ekranda görüntülenerek sona ermektedir. Eğer, parametre sayısı doğru ise, herbiri ilgili değişkenlere atanmaktadır. Ardından, '~/.ssh/agent_info' dosyasının varlığı denetlenmektedir ve eğer var ise, aktif hale getirilmektedir. Eğer, ssh-agent'ı kullanmıyorsanız, geçişsözcüklerini veya geçişcümlelerini elle girmeniz gerekecek ki, bu durumda otomatileştirmeden söz edemeyiz. Daha sonra, dosyalar (hosts, files ve commands) var olup olmadığı denetlenmektedir. files_empty.txt ve commands_empty.txt isimleri özel olarak denetlenmektedir. Eğer, bunlar verilmişse, dosyanın varlığının denetlenmesi gerekmemektedir. Programın bu kısmını bu yazının yazımı sırasında değiştirdim. Eski bu kısım aşağıdaki gibiydi:
if [ -f $host_file -a -f $files_file -a -f $commands_file ]; then echo "$host_file $files_file $commands_file" doit else echo "$host_file or $files_file or $commands_file does not exist" exit fi
Bu durumda files_empty.txt veya commands_empty.txt dosyaların var olması
gerekir. Yaptığım bütün işlemleri tek bir dizinde olduğu için bu dosyaları
bir kere yaratmak yeterli olmuştu ve benim işimi görmüştü.
Sonunda 'doit' fonksiyonu çalıştırılmaktadır. Herşey bu fonksiyon
içerisinden yönetilmektedir. Fonksiyonda, '$hosts_file' dosyası içerisinde
yer alan her bilgisayar için copy_files ve execute_commands fonksiyonları
çalıştırılmaktaır. Bu işlem 'cat' ve 'while'den oluşan bir döngü
içerisinde olmaktadır. Dolayısıyla hebir bilgisayar için iş yerine
getirilmiş olur.
Şimdi copy_files fonksiyonuna bir gözatalım. Bu fonksiyonda ilk önce
'files_file' değişkeninin değerinin 'files_empty.txt' değerine eşit olup
olmadığına bakılmaktadır. Eğer, eşit ise, hiç bir şey yapılmamaktadır.
Eğer, değil ise, '$files_file' dosyasındaki her satır için 'direction'
(yön), 'file1' ve 'file2' değişkenlerine değerleri atanmakta ve ardından
aktarım yönüne ('direction') göre aktarım işlemi scp ile
yapılmaktadır.
Son olarak execute_commands foksiyonunu inceleyelim. Foksiyon ilk önce
'commands_file' değişkeninin değerinin 'commands_empty.txt' olup
olmadığını denetlemektedir. Eğer, eşit ise, hiçbir işlem yapılmamaktadır.
Eğer, değil ise, '$commands_file' dosyasında yer alan her komut arka
planda çalıştırmaktadır. Komutu çalıştırdıktan sonra wait komutu '$!'
parametresi ile çalıştırılmaktadır. wait programını bu şekilde
çalıştırmakla her komutu ardışık olarak ve birinin diğerinden sonra
gelecek şekilde çalıştırmaktadır. '$!' nın değeri en son arka planda
çalıştırılan programın işlem numarasına eşit olmaktadır.
İşte bu kadar. Kolay, öyle değil mi?
Yukarıdaki programın daha gelişmiş bir kullanım şekli aşağıdaki programda uer almaktadır. Buradaki amaç, bilgisayarlarınızın yapılandırım dosyalarının yedeğini almaktır. Bunun için ainstall.sh programını kullanan basit bir kabuk programı yazdım:
#!/bin/sh server_dir=${HOME}/erdal/sh/ServerBackups if [ ! -d $server_dir ]; then echo "Directory : $server_dir does not exists." exit 1 fi cd $server_dir servers=ll_servers.txt prog=${HOME}/erdal/sh/einstall_sa.sh cat $servers | grep -v "#" | while read -r host do echo $host > host.txt $prog 1 host.txt files_empty.txt servers/${host}/commands_make_backup.txt $prog 1 host.txt files_getbackup.txt commands_empty.txt mv -f backup.tgz servers/${host}/backup/`date +%Y%m%d`.tgz rm -f host.txt done exit 0
servers adında bir dizine sahip olmanız gerekmektedir. Bu dizin içinde files_getbackup.txt ve ll_servers.txt adında iki dosya olmak zorundadır. 'files_getbackup.txt' dosyasının içeriği aşağıdaki gibidir:
r2l /root/backup.tgz backup.tgz
'll_servers.txt' dosyasında yedeği alınacak bilgisayarların adları veya IP numaraları yer almaktadır. 'll_servers.txt' dosyasında yer alan her bilgisayar adı veya IP numarası için aynı isimde 'servers' dizini altında bir dizin olması gerekmektedir. Bu dizinlerde /root/backup.tgz arşivini oluşturmaya yarayan 'commands_make_backups.txt' dosyası ve yedeklerin saklanacağı 'backups' dizini olmak zorundadır. Eğer, 'll_servers.txt' dosyası aiağıdaki gibi ise:
fileserver dbserver 10.10.10.1 appserver |
'$servers' dizin yapısı aşağıdaki gibi olmalıdır:
servers |-- files_getbackup.txt |-- ll_servers.txt |-- make_server_backups.sh |-- 10.10.10.1 | |-- backup | `-- commands_make_backup.txt |-- appserver | |-- backup | `-- commands_make_backup.txt |-- dbserver | |-- backup | `-- commands_make_backup.txt |-- fileserver |-- backup `-- commands_make_backup.txt |
'commands_make_backups.txt' dosyasına birkaç örnek aşağıda verilmiştir:
tar cfz /root/backup.tgz /etc/samba /etc/atalk /etc/named.conf /var/named/zones
Yukarıdaki dosyada samba, atalk ve isim sunucusu dosyalarının yedeğini alan komut yer almaktadır.
tar cfz /root/backup.tgz /etc/httpd /usr/local/apache
Yukarıdaki dosyada apache sunucusunun yedeğini alan komut yer almaktadır.
tar cfz /root/backup.tgz /etc/squid /etc/named.conf
Yukarıdaki dosyada squid ve ikincil isim sunucularının yedeğini alan komut yer almaktadır.
Yukarıdaki programı kullanarak ve gereksinimleriniz doğrultusunda 'commands_make_backup.txt' komutları oluşturarak sunucularınızın yedeklerini alabilirsiniz.