Описывая участие в проекте по модернизации VoIP оператора связи Часть 1 и Часть 2, одной из задач, которая выпала из поля зрения, было создание унифицированного инструмента для визуализации и мониторинга работы сервера Asterisk. По сути, после выхода из данного проекта, навязчивая идея привести отображение информации Asterisk к более удобному виду вылилась в проект создания прототипа унифицированной виртуальной файловой системы, объединяющей возможности всех разрозненных инструментов доступных в Asterisk.
Думаю что многие из администраторов, которые имели дело с Asterisk, зачастую удивлялись тому количеству различных команд, при помощи которых из Asterisk можно получать данные. Речь пойдёт об учётных записях для абонентских устройств, пользователях для аутентификации, каналах, а также о нестандартном применении виртуальных файловых систем.
Остановимся подробнее на различных методах получения данных.
Первый самый распространённый.
asterisk-macomnet*CLI> sip show peers
Name/username Host Dyn Forcerport Comedia ACL Port Status Description Realtime
6001 (Unspecified) D Auto (No) No 0 Unmonitored
person/person (Unspecified) D Auto (No) No 0 UNKNOWN
3 sip peers [Monitored: 0 online, 1 offline Unmonitored: 1 online, 1 offline]
asterisk-macomnet*CLI> sip show peer 6001
* Name : 6001
Description :
Realtime peer: No
Secret : <Set>
MD5Secret : <Not set>
Remote Secret: <Not set>
Context : web
Record On feature : automon
Record Off feature : automon
Subscr.Cont. : <Not set>
Language : ru
Tonezone : <Not set>
AMA flags : Unknown
Transfer mode: open
CallingPres : Presentation Allowed, Not Screened
Callgroup :
Pickupgroup :
Named Callgr :
Nam. Pickupgr:
MOH Suggest :
Mailbox :
VM Extension : asterisk
LastMsgsSent : 0/0
Call limit : 0
Max forwards : 0
Dynamic : Yes
Callerid : "6001" <6001>
MaxCallBR : 384 kbps
Expire : -1
Insecure : no
Force rport : Auto (No)
Symmetric RTP: No
ACL : No
DirectMedACL : No
T.38 support : No
T.38 EC mode : Unknown
T.38 MaxDtgrm: 4294967295
DirectMedia : No
PromiscRedir : No
User=Phone : No
Video Support: No
Text Support : No
Ign SDP ver : No
Trust RPID : No
Send RPID : No
Path support : No
Path : N/A
TrustIDOutbnd: Legacy
Subscriptions: Yes
Overlap dial : No
DTMFmode : rfc2833
Timer T1 : 500
Timer B : 32000
ToHost :
Addr->IP : (null)
Defaddr->IP : (null)
Prim.Transp. : UDP
Allowed.Trsp : UDP,WS
Def. Username:
SIP Options : (none)
Codecs : (gsm|g729|ulaw|alaw)
Auto-Framing : No
Status : Unmonitored
Useragent :
Reg. Contact :
Qualify Freq : 60000 ms
Keepalive : 0 ms
Sess-Timers : Accept
Sess-Refresh : uas
Sess-Expires : 1800 secs
Min-Sess : 90 secs
RTP Engine : asterisk
Parkinglot :
Use Reason : No
Encryption : Yes
asterisk-macomnet*CLI> sip show users
Username Secret Accountcode Def.Context ACL Forcerport
6001 MegaPass12345 web No No
person E346fz8Vam users_context No No
asterisk-macomnet*CLI> sip show user 6001
* Name : 6001
Secret : <Set>
MD5Secret : <Not set>
Context : web
Language : ru
AMA flags : Unknown
Tonezone : <Not set>
Transfer mode: open
MaxCallBR : 384 kbps
CallingPres : Presentation Allowed, Not Screened
Call limit : 0
Callgroup :
Pickupgroup :
Named Callgr :
Nam. Pickupgr:
Callerid : "6001" <6001>
ACL : No
Sess-Timers : Accept
Sess-Refresh : uas
Sess-Expires : 1800 secs
Sess-Min-SE : 90 secs
RTP Engine : asterisk
Auto-Framing: No
Не правда ли знакомые команды? И многие ими пользуются почти ежедневно. Ни для кого наверное не является секретом, что в Asterisk есть дерево объектов. Доступ к нему предоставляется через провайдеров. Информация в дереве полнее чем мы видели раньше.
asterisk-macomnet*CLI> data show providers
/asterisk/channel/iax2/peers (get) [chan_iax2.c]
/asterisk/channel/iax2/users (get) [chan_iax2.c]
/asterisk/channel/dahdi/version (get) [chan_dahdi.c]
/asterisk/channel/dahdi/status (get) [chan_dahdi.c]
/asterisk/channel/dahdi/channels (get) [chan_dahdi.c]
/asterisk/channel/sip/peers (get) [chan_sip.c]
/asterisk/core/hints (get) [pbx.c]
/asterisk/core/channels (get) [channel.c]
/asterisk/core/channeltypes (get) [channel.c]
/asterisk/application/queue/list (get) [app_queue.c]
Доступ к дереву объектов, позволяет иметь полную информацию о каждом из них.
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers
peers
peer
vmexten: "asterisk"
is_realtime: False
amaflags
text: "Unknown"
value: 0
transports: "UDP,WS"
inuse: 0
timer_t1: 500
callingpres
text: "Presentation Allowed, Not Screened"
value: 0
unsolicited_mailbox: ""
host_dynamic: True
subscribecontext: ""
useragent: ""
rtptimeout: 0
cid_num: "6001"
mohinterpret: "default"
parkinglot: ""
mwi_from: ""
call_limit: 0
fullcontact: ""
rtpholdtimeout: 0
qualifyfreq: 60000
maxms: 0
rtpkeepalive: 0
allowtransfer
text: "open"
value: 0
regexten: ""
onhold: 0
ringing: 0
fromdomain: ""
timer_b: 32000
fromuser: ""
username: ""
secret: "MegaPass12345"
accountcode: ""
t38_maxdatagram: -1
remotesecret: ""
md5secret: ""
maxcallbitrate: 384
mohsuggest: ""
lastms: 0
sipoptions
sec_agree: False
histinfo: False
join: False
resource-priority: False
precondition: False
eventlist: False
outbound: False
recipient-list-invite: False
early-session: False
recipient-list-subscribe: False
sdp-anat: False
100rel: False
replaces: False
from-change: False
replace: False
gruu: False
tdialog: False
privacy: False
norefersub: False
timer: False
path: False
pref: False
tohost: ""
autoframing: False
name: "6001"
cid_name: "6001"
language: "ru"
context: "web"
description: ""
type: "friend"
engine: "asterisk"
codecs
codec
frame_length: 33
samplespersecond: 8000
name: "gsm"
description: "GSM"
codec
frame_length: 10
samplespersecond: 8000
name: "g729"
description: "G.729A"
codec
frame_length: 80
samplespersecond: 8000
name: "ulaw"
description: "G.711 u-law"
codec
frame_length: 80
samplespersecond: 8000
name: "alaw"
description: "G.711 a-law"
peer
vmexten: "asterisk"
is_realtime: False
amaflags
text: "Unknown"
value: 0
transports: "UDP,WS"
inuse: 0
timer_t1: 500
callingpres
text: "Presentation Allowed, Not Screened"
value: 0
unsolicited_mailbox: ""
host_dynamic: True
subscribecontext: ""
useragent: ""
rtptimeout: 0
cid_num: "person"
mohinterpret: "default"
parkinglot: ""
mwi_from: ""
call_limit: 0
fullcontact: ""
rtpholdtimeout: 0
qualifyfreq: 60000
maxms: 2000
rtpkeepalive: 0
allowtransfer
text: "open"
value: 0
regexten: ""
onhold: 0
ringing: 0
fromdomain: ""
timer_b: 32000
fromuser: ""
username: "person"
secret: "E346fz8Vam"
accountcode: ""
t38_maxdatagram: -1
remotesecret: ""
md5secret: ""
maxcallbitrate: 384
mohsuggest: ""
lastms: 0
sipoptions
sec_agree: False
histinfo: False
join: False
resource-priority: False
precondition: False
eventlist: False
outbound: False
recipient-list-invite: False
early-session: False
recipient-list-subscribe: False
sdp-anat: False
100rel: False
replaces: False
from-change: False
replace: False
gruu: False
tdialog: False
privacy: False
norefersub: False
timer: False
path: False
pref: False
tohost: ""
autoframing: False
name: "person"
cid_name: "person"
language: "en"
context: "users_context"
description: ""
type: "friend"
engine: "asterisk"
codecs
codec
frame_length: 33
samplespersecond: 8000
name: "gsm"
description: "GSM"
codec
frame_length: 10
samplespersecond: 8000
name: "g729"
description: "G.729A"
codec
frame_length: 80
samplespersecond: 8000
name: "ulaw"
description: "G.711 u-law"
codec
frame_length: 80
samplespersecond: 8000
name: "alaw"
description: "G.711 a-law"
Наверное мало кто знает, из-за отсутствия нормальной документации на команду "data get", но в ней есть поиск и группировка полей.
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers peers/peer/name=6001
или
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers peers/peer/secret=MegaPass12345
peers
peer
vmexten: "asterisk"
is_realtime: False
amaflags
text: "Unknown"
value: 0
transports: "UDP,WS"
inuse: 0
timer_t1: 500
callingpres
text: "Presentation Allowed, Not Screened"
value: 0
unsolicited_mailbox: ""
host_dynamic: True
subscribecontext: ""
useragent: ""
rtptimeout: 0
cid_num: "6001"
mohinterpret: "default"
parkinglot: ""
mwi_from: ""
call_limit: 0
fullcontact: ""
rtpholdtimeout: 0
qualifyfreq: 60000
maxms: 0
rtpkeepalive: 0
allowtransfer
text: "open"
value: 0
regexten: ""
onhold: 0
ringing: 0
fromdomain: ""
timer_b: 32000
fromuser: ""
username: ""
secret: "MegaPass12345"
accountcode: ""
t38_maxdatagram: -1
remotesecret: ""
md5secret: ""
maxcallbitrate: 384
mohsuggest: ""
lastms: 0
sipoptions
sec_agree: False
histinfo: False
join: False
resource-priority: False
precondition: False
eventlist: False
outbound: False
recipient-list-invite: False
early-session: False
recipient-list-subscribe: False
sdp-anat: False
100rel: False
replaces: False
from-change: False
replace: False
gruu: False
tdialog: False
privacy: False
norefersub: False
timer: False
path: False
pref: False
tohost: ""
autoframing: False
name: "6001"
cid_name: "6001"
language: "ru"
context: "web"
description: ""
type: "friend"
engine: "asterisk"
codecs
codec
frame_length: 33
samplespersecond: 8000
name: "gsm"
description: "GSM"
codec
frame_length: 10
samplespersecond: 8000
name: "g729"
description: "G.729A"
codec
frame_length: 80
samplespersecond: 8000
name: "ulaw"
description: "G.711 u-law"
codec
frame_length: 80
samplespersecond: 8000
name: "alaw"
description: "G.711 a-law"
Запрос данных из дерева поддерживает фильтрацию параметров. При этом параметр поиска должен иметь значение "1=1", любое другое условие поиска описанное выше игнорируется автоматически.
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers 1=1 peers/peer/cid_num,peers/peer/name,peers/peer/username,peers/peer/secret,peers/peer/lastms,peers/peer/fullcontact
peers
peer
cid_num: "6001"
fullcontact: "sip:6001@192.168.200.247:5060"
username: ""
secret: "MegaPass12345"
lastms: 0
name: "6001"
peer
cid_num: "person"
fullcontact: ""
username: "person"
secret: "E346fz8Vam"
lastms: 0
name: "person"
Доступ к дереву это очень удобно, но дерево по умолчанию содержит только записи жёстко прописанные в конфигурационных файлах либо записи кешированных realtime подключений. Для получения полноценного дерева необходимо загрузить из базы данных все параметры всех realtime подключений.
Отображение данных realtime пиров производится командой:
asterisk-macomnet*CLI> sip show peer <name> load
Usage: sip show peer <name> [load]
Shows all details on one SIP peer and the current status.
Option "load" forces lookup of peer in realtime storage.
К сожалению для того, чтобы загрузить данные, нужно знать что загружать.
asterisk-macomnet*CLI> realtime load sippeers name zhecka
Usage: realtime load <family> <colmatch> <value>
Prints out a list of variables using the RealTime driver.
You must supply a family name, a column to match on, and a value to match to.
Как мы здесь видим, для загрузки данных realtime подключения необходимо также знать что грузить. Но есть "грязный хак". Параметры "colmatch" и "value" передаются в sql провайдер "как есть". Поэтому можно сделать запрос вида "realtime load sippeers 1 1" и получить все данные за один запрос.
asterisk-macomnet*CLI> realtime load sippeers 1 1
Column Name Column Value
-------------------- --------------------
dtlssetup actpass
id 1325
name zhecka
ipaddr
port 0
regseconds 0
defaultuser zhecka
fullcontact
regserver
useragent
lastms 0
host dynamic
type friend
context web
permit
deny
secret zhecka
md5secret
remotesecret
transport udp,tcp
dtmfmode
directmedia no
nat
callgroup
pickupgroup
language
disallow
allow
insecure
trustrpid
progressinband
promiscredir
useclientcode
accountcode
setvar
callerid
amaflags
callcounter
busylevel
allowoverlap
allowsubscribe
videosupport
maxcallbitrate
rfc2833compensate
mailbox
session-timers
session-expires
session-minse
session-refresher
t38pt_usertpsource
regexten
fromdomain
fromuser
qualify
defaultip
rtptimeout
rtpholdtimeout
sendrpid
outboundproxy
callbackextension
timert1
timerb
qualifyfreq
constantssrc
contactpermit
contactdeny
usereqphone
textsupport
faxdetect
buggymwi
auth
fullname
trunkname
cid_number
callingpres
mohinterpret
mohsuggest
parkinglot
hasvoicemail
subscribemwi
vmexten
autoframing
rtpkeepalive
call-limit
g726nonstandard
ignoresdpversion
allowtransfer
dynamic
path
supportpath
encryption no
avpf yes
force_avp yes
icesupport yes
dtlsenable yes
dtlsverify fingerprint
dtlscertfile /usr/local/share/asterisk/keys/cert.crt
dtlsprivatekey /usr/local/share/asterisk/keys/private.pem
dtlscafile /usr/local/share/asterisk/keys/ca.crt
dtlssetup actpass
id 1327
name zhecka1
ipaddr
port
regseconds
defaultuser zhecka1
fullcontact
regserver
useragent
lastms
host dynamic
type friend
context web
permit
deny
secret
md5secret
remotesecret
transport ws
dtmfmode
directmedia no
nat
callgroup
pickupgroup
language
disallow
allow
insecure
trustrpid
progressinband
promiscredir
useclientcode
accountcode
setvar
callerid
amaflags
callcounter
busylevel
allowoverlap
allowsubscribe
videosupport
maxcallbitrate
rfc2833compensate
mailbox
session-timers
session-expires
session-minse
session-refresher
t38pt_usertpsource
regexten
fromdomain
fromuser
qualify
defaultip
rtptimeout
rtpholdtimeout
sendrpid
outboundproxy
callbackextension
timert1
timerb
qualifyfreq
constantssrc
contactpermit
contactdeny
usereqphone
textsupport
faxdetect
buggymwi
auth
fullname
trunkname
cid_number
callingpres
mohinterpret
mohsuggest
parkinglot
hasvoicemail
subscribemwi
vmexten
autoframing
rtpkeepalive
call-limit
g726nonstandard
ignoresdpversion
allowtransfer
dynamic
path
supportpath
encryption yes
avpf yes
force_avp yes
icesupport yes
dtlsenable yes
dtlsverify fingerprint
dtlscertfile /usr/local/share/asterisk/keys/cert.crt
dtlsprivatekey /usr/local/share/asterisk/keys/private.pem
dtlscafile /usr/local/share/asterisk/keys/ca.crt
dtlssetup actpass
Данный запрос покажет все realtime пиры, которые имеются в базе данных.
Далее необходимо по имени каждого пира выполнить команду
asterisk-macomnet*CLI> sip show peer zhecka load
* Name : zhecka
Description :
Realtime peer: Yes, cached
Secret : <Set>
MD5Secret : <Not set>
Remote Secret: <Not set>
Context : web
Record On feature : automon
Record Off feature : automon
Subscr.Cont. : <Not set>
Language : ru
Tonezone : <Not set>
AMA flags : Unknown
Transfer mode: open
CallingPres : Presentation Allowed, Not Screened
Callgroup :
Pickupgroup :
Named Callgr :
Nam. Pickupgr:
MOH Suggest :
Mailbox :
VM Extension : asterisk
LastMsgsSent : 0/0
Call limit : 0
Max forwards : 0
Dynamic : Yes
Callerid : "" <>
MaxCallBR : 384 kbps
Expire : 120
Insecure : no
Force rport : Auto (No)
Symmetric RTP: No
ACL : No
DirectMedACL : No
T.38 support : No
T.38 EC mode : Unknown
T.38 MaxDtgrm: 4294967295
DirectMedia : No
PromiscRedir : No
User=Phone : No
Video Support: No
Text Support : No
Ign SDP ver : No
Trust RPID : No
Send RPID : No
Path support : No
Path : N/A
TrustIDOutbnd: Legacy
Subscriptions: Yes
Overlap dial : No
DTMFmode : rfc2833
Timer T1 : 500
Timer B : 32000
ToHost :
Addr->IP : (null)
Defaddr->IP : (null)
Prim.Transp. : UDP
Allowed.Trsp : UDP,TCP
Def. Username: zhecka
SIP Options : (none)
Codecs : (ulaw|alaw|gsm|h263)
Auto-Framing : No
Status : Unmonitored
Useragent :
Reg. Contact :
Qualify Freq : 60000 ms
Keepalive : 0 ms
Sess-Timers : Accept
Sess-Refresh : uas
Sess-Expires : 1800 secs
Min-Sess : 90 secs
RTP Engine : asterisk
Parkinglot :
Use Reason : No
Encryption : No
После этих операций во внутреннем дереве данных появится запись realtime пира "zhecka", находящегося в данный момент в "offline"
asterisk-macomnet*CLI> data get /asterisk/channel/sip/peers 1=1 peers/peer/cid_num,peers/peer/name,peers/peer/username,peers/peer/secret,peers/peer/lastms,peers/peer/fullcontact
peers
peer
cid_num: ""
fullcontact: ""
username: "zhecka"
secret: "zhecka"
lastms: 4294967295
name: "zhecka"
peer
cid_num: "6001"
fullcontact: ""
username: ""
secret: "MegaPass12345"
lastms: 0
name: "6001"
peer
cid_num: "person"
fullcontact: ""
username: "person"
secret: "E346fz8Vam"
lastms: 0
name: "person"
Как видно из представленной последовательности, для получения данных необходимо проделать несколько различных операций для каждого имеющегося в realtime базе пира.
Наверное многие из Вас скажут: "Есть же REST! используй его!"
Отвечу: "Да. REST есть. Но он слабоват и не даёт нужной информации"
Вывод REST запроса http://localhost:8088/ari/endpoints/SIP
[
{
"technology": "SIP",
"resource": "person",
"state": "unknown",
"channel_ids": []
},
{
"technology": "SIP",
"resource": "mtrf",
"state": "unknown",
"channel_ids": []
},
{
"technology": "SIP",
"resource": "6001",
"state": "unknown",
"channel_ids": []
}
]
В выводе к сожалению отсутствуют нужные поля и для их добавления необходимо переписать или дописать REST модуль.
Итак, что же предлагается? А предлагается перенести дерево Asterisk в файловую систему на манер devfs/procfs и отслеживать все настройки в одном месте.
Выглядит это примерно вот так.
# tree /mnt
/mnt
├── config -> /usr/local/etc/asterisk
├── peers
│ ├── iax2
│ │ ├── corbina
│ │ │ └── full
│ │ └── msm
│ │ └── full
│ └── sip
│ ├── _offline
│ │ └── mtrf -> ../mtrf
│ ├── _online
│ └── mtrf
│ ├── codecs
│ ├── contexts
│ ├── credentials
│ ├── full
│ └── huntgroups
└── users
├── iax2
│ └── macomnet
│ └── full
├── sipfriends
│ ├── 6001
│ │ ├── codecs
│ │ ├── contexts
│ │ ├── credentials
│ │ ├── full
│ │ └── huntgroups
│ ├── _offline
│ │ ├── 6001 -> ../6001
│ │ └── person -> ../person
│ ├── _online
│ │ └── zhecka -> ../zhecka
│ ├── person
│ │ ├── codecs
│ │ ├── contexts
│ │ ├── credentials
│ │ ├── full
│ │ └── huntgroups
│ └── zhecka
│ ├── codecs
│ ├── contexts
│ ├── credentials
│ ├── full
│ └── huntgroups
└── sipusers
├── _offline
│ └── zhecka1 -> ../zhecka1
├── _online
└── zhecka1
├── codecs
├── contexts
├── credentials
├── full
└── huntgroups
Это реальный слепок файловой системы тестового сервера Asterisk. Так как мы не ограничены в форматировании выдаваемых данных, то выдача параметров для подключения будет выглядеть следующим образом.
# cat /mnt/users/sipfriends/_online/zhecka/credentials
name=zhecka
username=zhecka
secret=zhecka
md5secret=
fromuser=
fromdomain=
cid_name=
remotesecret=
fullcontact=sip:zhecka@10.6.207.135:64802;transport=udp;rinstance=d42eec40501a7c4d
useragent=Bria release 2.5 RC4 stamp 47242
regexten=
transports=UDP,TCP
Или контексты
# cat /mnt/users/sipfriends/_online/zhecka/contexts
context=web
subscribecontext=
Все эти данные получаются в реальном режиме времени, ну или с минимальной задержкой при обработке Asterisk Events, во время отключения/подключения устройства.
Как это работает? AstFS написана на языке Python и использует библиотеку Fuse.
Библиотека Fuse позволяет на каждый соответствующий event (open, readdir, getattr, close и т.д.), приходящий от файловой системы, вызвать процедуру обрабатывающую входящие и формирующую конечные данные пользователя.
Так как библиотека Fuse как и большинство event based библиотек имеет внутренний loop, то передать ей данные с внешней программы довольно тяжело. Необходим общий объект или pipe для работы.
С точки зрения использования TCP/UDP socket pipe или чего-то похожего, в случае отказа сервера или демона обеспечивающего доставку данных, запрос к файловой системе бесконтрольно повиснет. Опять же из-за собственного внутреннего цикла библиотека не может быть корректно внедрена в асинхронные фреймворки типа twisted(у меня не получилось по крайней мере). Попытка завести всё через Threading вызвала кучу разных проблем и от этой идеи пришлось отказаться. Вызов соединения из процедур напрямую к Asterisk вызывал дичайший оверхед, т.к. на каждый "чих" необходимо было открывать новое соединение к AMI.
Единственным более менее нормальным решением было разделение процесса обслуживающего запросы к файловой системе и процесса получающего данные из Asterisk используя multiprocessing. У данного компонента имеется возможность контроля за порождаемыми процессами, а также имеются объекты, которые можно "шарить" между процессами.
Таким образом система состоит из трёх процессов:
- Manager
- FuseFS
- Asterisk Event Handler
Между ними "шарится" общий словарь упакованный в JSON. Дело в том, что общие объекты имеют структуру отличную от python структур и с ними не совместимы. При обновлении данных из Asterisk, данный словарь распаковывается, обновляется и запаковывается обратно.
Соответственно процесс обслуживающий запросы из файловой системы при необходимости распаковывает словарь и формирует конечные данные для пользователя.
Исходный код AstFS содержит все необходимые классы и пример для создания виртуальной файловой системы. Код конечно далёк от идеала, но будет модифицироваться в лучшую сторону по возможности(есть коментарии на русском).
Основной упор был сделан на гибкость структуры файловой системы.
Под гибкостью подразумевается, что в качестве обработчика ветки дерева или файла может использоваться либо класс либо функция либо просто статические данные.
Определение дерева довольно простое:
root = {
'helpfilefunc': [fsstruct.stat_info['file'], fusefscore.returnhelp],
'link_to_tmp': [fsstruct.stat_info['link'], '/tmp'],
'staticfile': [fsstruct.stat_info['file'], 'contents of staticfile'],
'customchmodfile': [{'st_mode': 0x8000 | int('0600', 8), 'st_uid': 0, 'st_gid': 0}, 'contents of customchmodfile'],
'nulldir': {},
'nullfile': [{}, ''],
'demotreeclass': fsstruct.BasicTree('/demotreeclass', mpd, ''),
'tree': {'peer': [{'st_mode': 0x8000 | int('0644', 8)}, 'contents of peer'], 'friend': {}, 'user': {}}
}
# tree -ghpu /mnt/
/mnt/
├── [-rw------- root wheel 27] customchmodfile
├── [drwxr-xr-x asterisk asterisk 512] demotreeclass
│ ├── [-rw-r--r-- asterisk asterisk 12] help
│ └── [-rw-r--r-- asterisk asterisk 12] info
├── [-rw-r--r-- asterisk asterisk 33] helpfilefunc
├── [lrwxr-xr-x asterisk asterisk 4] link_to_tmp -> /tmp
├── [drwxr-xr-x asterisk asterisk 512] nulldir
├── [-rw-r--r-- asterisk asterisk 0] nullfile
├── [-rw-r--r-- asterisk asterisk 22] staticfile
└── [drwxr-xr-x asterisk asterisk 512] tree
├── [drwxr-xr-x asterisk asterisk 512] friend
├── [-rw-r--r-- asterisk asterisk 16] peer
└── [drwxr-xr-x asterisk asterisk 512] user
6 directories, 7 files
Если нужно определить файл, то просто задаётся массив (list) состоящий из двух параметров. Первый это тип файла и права доступа к нему, второй это либо статическое содержимое файла, либо имя функции возвращающей содержимое файла. Если нужно определить директорию, то просто определяется словарь (dict) содержащий в свою очередь либо список файлов, либо список директорий. При определении директории в которой находится динамически изменяемая структура, можно задать подкласс, который будет обрабатывать все запросы файловой системы.
По сути для базового функционала в классе достаточно описать 4 метода:
- getattr
- read
- readdir
- readlink
Остановимся поподробнее об особенностях реализации.
getattr вызывается для определения типа файла или директории, возвращает только атрибуты! Никакой другой информации из данного метода не получается.
readdir вызывается при открытии директории и запросе файлов, возвращает только список файлов находящихся в директории без атрибутов!
При обычном вызове списка файлов
# ls /mnt
customchmodfile demotreeclass helpfilefunc link_to_tmp nulldir nullfile staticfile tree
происходит следующая последовательность
2017-03-14 13:13:04,559 - DEBUG - [FuseFSCore.getattr] path / fh None. Context: {'gid': 0, 'pid': 99276, 'uid': 0}
2017-03-14 13:13:04,559 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}
2017-03-14 13:13:04,560 - DEBUG - [FuseFSCore.getattr] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:13:04,560 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object / is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 9, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:13:04,561 - DEBUG - [FuseFSCore.statfs] Request FileSystem Info. Context: {'gid': 0, 'pid': 99276, 'uid': 0}
2017-03-14 13:13:04,561 - DEBUG - [FuseFSCore.readdir] Path /:0. Context: {'gid': 0, 'pid': 99276, 'uid': 0}
2017-03-14 13:13:04,562 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}
2017-03-14 13:13:04,562 - DEBUG - [FuseFSCore.readdir] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:13:04,562 - DEBUG - [FuseFSCore.readdir] SrcTree: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}, DirTree: <type 'dict'>:{'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}
- через getattr запрашивается наличие файла или пути "/"
- функция обрабатывающая дерево файлов returntree возвращает содержимое запрошенной ветки дерева с добавлением элемента '__dirstat__', который в последующих функциях либо выдаётся в качестве ответа на getattr, либо удаляется если запрашивается список файлов в директории.
- на выходе из getattr удаляется список файлов и системе возвращаются только атрибуты директории.
- запрашивается размер блока на томе через statfs
- система запрашивает содержимое директории "/"
- returntree возвращает список файлов с атрибутами директории.
- readdir убирает атрибуты директории из списка и отдаёт системе только список файлов.
Попробуем открыть файл
2017-03-14 13:26:45,137 - DEBUG - [FuseFSCore.open] /staticfile:0. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,138 - DEBUG - [FuseFSCore.getattr] path /staticfile fh None. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,139 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,139 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,140 - DEBUG - [FuseFSCore.getattr] Found data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,140 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,140 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /staticfile is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,141 - DEBUG - [FuseFSCore.read] read file /staticfile:4096:0. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,141 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,142 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,142 - DEBUG - [FuseFSCore.read] dirtree data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,142 - DEBUG - [FuseFSCore.read] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.read] read file /staticfile:4096:22. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,143 - DEBUG - [FuseFSCore.read] dirtree data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.read] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.truncate] /staticfile:22:None. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.getattr] path /staticfile fh None. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,144 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile']
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.getattr] Found data [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, 'contents of staticfile'] for path /staticfile
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /staticfile is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 33188, 'st_dev': 0, 'st_size': 22, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:26:45,145 - DEBUG - [FuseFSCore.release] /staticfile:0. Context: {'gid': 0, 'pid': 99297, 'uid': 0}
Последовательность похожа.
- через open указывается имя файла
- через getattr запрашиваются атрибуты файла
- производится проверка файла на чтение с нулевым размером читаемого блока, если всё хорошо — производится чтение блока из файла равного размеру блока файловой системы или если размер файла меньше, то размеру файла.
- файл закрывается и освобождается
Так работает статическая структура. А что же с динамической ?
Откроем директорию /mnt/demotreeclass, обработчик которой ссылается на класс
fsstruct.BasicTree.
2017-03-14 13:29:16,819 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99300, 'uid': 0}
2017-03-14 13:29:16,819 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:29:16,819 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:29:16,820 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:29:16,820 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:29:16,820 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:29:16,821 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:29:16,821 - DEBUG - [FuseFSCore.statfs] Request FileSystem Info. Context: {'gid': 0, 'pid': 99300, 'uid': 0}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] Path /demotreeclass:0. Context: {'gid': 0, 'pid': 99300, 'uid': 0}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:29:16,822 - DEBUG - [BasicTree._readdir] path len 1, path ['']
2017-03-14 13:29:16,822 - DEBUG - [BasicTree._readdir] return full tree {'info': {}, 'help': {}}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'info': {}, 'help': {}}
2017-03-14 13:29:16,822 - DEBUG - [FuseFSCore.readdir] SrcTree: <astfs.fsstruct.BasicTree object at 0x8058fe210>, DirTree: <type 'dict'>:{'info': {}, 'help': {}}
Происходит всё тоже самое, что и при открытии статической директории, только при обходе дерева объектов в структуре директории, обработчик видит, что ссылка на директорию — это класс и передаёт управление ему. Далее класс сам в зависимости от запрашиваемой операции формирует ответ и отдаёт его основному процессу.
Сложнее всего дело обстоит если кто-то запрашивает команду
# ls -al /mnt/demotreeclass
total 2
drwxr-xr-x 1 asterisk asterisk 512 14 мар 11:10 .
drwxr-xr-x 9 asterisk asterisk 512 14 мар 11:10 ..
-rw-r--r-- 1 asterisk asterisk 12 14 мар 11:10 help
-rw-r--r-- 1 asterisk asterisk 12 14 мар 11:10 info
2017-03-14 13:40:46,539 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,540 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,540 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,540 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,540 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,541 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,541 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,541 - DEBUG - [FuseFSCore.statfs] Request FileSystem Info. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,542 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,542 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,542 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,542 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,543 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,543 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,543 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,543 - DEBUG - [FuseFSCore.readdir] Path /demotreeclass:0. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,544 - DEBUG - [FuseFSCore.readdir] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,544 - DEBUG - [BasicTree._readdir] path len 1, path ['']
2017-03-14 13:40:46,544 - DEBUG - [BasicTree._readdir] return full tree {'info': {}, 'help': {}}
2017-03-14 13:40:46,544 - DEBUG - [FuseFSCore.readdir] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'info': {}, 'help': {}}
2017-03-14 13:40:46,545 - DEBUG - [FuseFSCore.readdir] SrcTree: <astfs.fsstruct.BasicTree object at 0x8058fe210>, DirTree: <type 'dict'>:{'info': {}, 'help': {}}
2017-03-14 13:40:46,545 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,545 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,545 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,546 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,546 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,546 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,546 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,546 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,547 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,547 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,547 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,547 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,547 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.getattr] path / fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}
2017-03-14 13:40:46,548 - DEBUG - [FuseFSCore.getattr] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object / is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 9, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] path / fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.returntree] Instance Dict > retdict: {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []}
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] Found data {'nullfile': [], 'demotreeclass': [], '__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}], 'link_to_tmp': [], 'helpfilefunc': [], 'tree': [], 'nulldir': [], 'customchmodfile': [], 'staticfile': []} for path /
2017-03-14 13:40:46,549 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object / is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 9, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] path /demotreeclass/info fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass/info
2017-03-14 13:40:46,550 - DEBUG - [BasicTree._getattr] started for path info
2017-03-14 13:40:46,550 - DEBUG - [BasicTree._getattr] path len 1, path ['info']
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 0, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}, <bound method BasicTree._getfile of <astfs.fsstruct.BasicTree object at 0x8058fe210>>]
2017-03-14 13:40:46,550 - DEBUG - [BasicTree._getfile] started for path info
2017-03-14 13:40:46,550 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,551 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass/info is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,551 - DEBUG - [FuseFSCore.getattr] path /demotreeclass/help fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,551 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass/help
2017-03-14 13:40:46,551 - DEBUG - [BasicTree._getattr] started for path help
2017-03-14 13:40:46,551 - DEBUG - [BasicTree._getattr] path len 1, path ['help']
2017-03-14 13:40:46,552 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 0, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}, <bound method BasicTree._getfile of <astfs.fsstruct.BasicTree object at 0x8058fe210>>]
2017-03-14 13:40:46,552 - DEBUG - [BasicTree._getfile] started for path help
2017-03-14 13:40:46,552 - DEBUG - [FuseFSCore.getattr] List <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,552 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass/help is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 12, 'st_ino': 0, 'st_uid': 931, 'st_mode': 33188, 'st_atime': 1489479051}
2017-03-14 13:40:46,553 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,554 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,554 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,554 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
2017-03-14 13:40:46,554 - DEBUG - [FuseFSCore.getattr] path /demotreeclass fh None. Context: {'gid': 0, 'pid': 99321, 'uid': 0}
2017-03-14 13:40:46,555 - DEBUG - [FuseFSCore.getattr] Found data <astfs.fsstruct.BasicTree object at 0x8058fe210> for path /demotreeclass
2017-03-14 13:40:46,555 - DEBUG - [BasicTree._getattr] started for path
2017-03-14 13:40:46,555 - DEBUG - [BasicTree._getattr] path len 1, path ['']
2017-03-14 13:40:46,555 - DEBUG - [BasicTree._getattr] return full tree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,555 - DEBUG - [FuseFSCore.getattr] Object <astfs.fsstruct.BasicTree object at 0x8058fe210> return dirtree {'__dirstat__': [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}, '']}
2017-03-14 13:40:46,555 - DEBUG - [FuseFSCore.getattr] Attrstat Result for object /demotreeclass is <type 'dict'>:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_gid': 931, 'st_dev': 0, 'st_size': 512, 'st_ino': 0, 'st_uid': 931, 'st_mode': 16877, 'st_atime': 1489479051}
В данном случае происходит рекурсивный запрос по всем объектам внутри директории и если их довольно много, то это может создать довольно существенную нагрузку на систему, особенно если включен дебаг.
За счёт того, что чтение файлов производится по возвращаемым системой атрибутам, возникает необходимость постоянного пересчёта размера файлов при запросах. Неверно выданный размер приводит к "обрезанию" файла либо ошибке файловой системы.
При обращении к ссылке на ресурс(symlink)
# ls /mnt/link_to_tmp
.ICE-unix aguilera.ulaw crontab.B0fGYRPx73~ macroform-the_simplicity.wav mycachedir pymp-Ri0pXJ pymp-eysf7M socket
.X11-unix cachedir crontab.SyEoUZMExj~ manolo_camp-morning_coffee.ulaw mysql.sock pymp-SZfCWa pymp-hH6kLC treecache
.XIM-unix crontab.1yo8gxs5HE~ errlog manolo_camp-morning_coffee.wav pymp-6oVKsj
2017-03-14 13:47:55,593 - DEBUG - [FuseFSCore.readlink] path: /link_to_tmp. Context: {'gid': 0, 'pid': 99341, 'uid': 0}
2017-03-14 13:47:55,594 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:47:55,594 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']
2017-03-14 13:47:55,594 - DEBUG - [FuseFSCore.readlink] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']
2017-03-14 13:47:55,595 - DEBUG - [FuseFSCore.readlink] path: /link_to_tmp. Context: {'gid': 0, 'pid': 99341, 'uid': 0}
2017-03-14 13:47:55,595 - DEBUG - [FuseFSCore.returntree] Instance List > itemstat:{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}
2017-03-14 13:47:55,595 - DEBUG - [FuseFSCore.returntree] data: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']
2017-03-14 13:47:55,596 - DEBUG - [FuseFSCore.readlink] FoundTree: [{'st_ctime': 1489479051, 'st_mtime': 1489479051, 'st_nlink': 1, 'st_mode': 41453, 'st_dev': 0, 'st_size': 0, 'st_gid': 931, 'st_uid': 931, 'st_ino': 0, 'st_atime': 1489479051}, '/tmp']
Происходят всё те же операции:
- через getattr запрашиваются атрибуты объекта
- система "видит", что объект является символьной ссылкой и вызывает функцию readlink
- readlink отдаёт в обычном текстовом виде адрес ресурса на который нужно перенаправить вызов.
Так в чём же красота решения?
Красота решения в том, что на любую ветку дерева можно навесить абсолютно любую логику. Скажем Вы хотите видеть любимый сайт торрентов в файловом виде у себя в системе. Вы просто создаёте класс, который на запрос данных директории "дёргает" трекер и выдаёт Вам список имеющихся торентов в виде списка файлов. При открытии или копировании нужного файла, класс просто "дёргает" нужный файл с трекера и отдаёт его Вам.
Или скажем у вас есть сервер с динамически строящимися отчётами. Вам нет необходимости хранить все отчёты на локальной файловой системе, т.к. они есть в базе. Просто создаётся класс, который смотрит в базу и при запросе выдаёт необходимую информацию о наличии отчётов. Или при запросе отчёта в режиме реального времени строит его.
Или у вас есть логи демонов, которые нужно обрабатывать реалтайм. Можно конечно повесить хендлер, который будет отслеживать изменение лога, или сделать pipe для отправки лога в какой-то скрипт. С помощью виртуальной файловой системы с помощью спецкласса Вы можете писать логи в базу напрямую если это необходимо, при этом уже в подготовленном виде.
Еще одно применение такого плана файловых систем это изоляция данных пользователей друг от друга. При обращении к файлу находящемуся в виртуальной файловой системе в процедуры передаётся переменное окружение в виде номера процесса, uid и gid пользователя. На основе этих данных можно формировать различное содержимое для одного и того же файла, т.е. указатель на файл один, а содержимое для всех разное, скажем файл с набором паролей для конкретного пользователя зашифрованный ключём этого пользователя. Речь конечно о многопользовательских системах, где на одном сервере работает куча народу.
Есть конечно и явный минус заключающийся в недостаточной скорости работы при большой вложенности, но чем-то всё равно приходится жертвовать.
Так что же всё-таки использование виртуальных файловых систем — это шаг назад или два вперед ?
PS: если кто пропустил ссылку на проект на github, вот она
© Aborche 2017
Автор: aborche