|
1 #!/usr/bin/python3 |
|
2 # coding=utf8 |
|
3 |
|
4 import os, sys, subprocess, random, string, traceback, urllib.parse |
|
5 from os.path import join |
|
6 from d import D |
|
7 from parms import Parms |
|
8 from node import Node |
|
9 |
|
10 |
|
11 # operace v nekonečném cyklu serveru: |
|
12 # - pro každý kanál v homedir je jedna instance Server |
|
13 # - každá instance má k dispozici 10 portů pro LISTEN |
|
14 # - číslování portů: |
|
15 # - 17CCP |
|
16 # - CC = číslo kanálu |
|
17 # - P = číslo portu v kanálu |
|
18 # - port 17xx0 je vyhrazen pro rychlé synchronní operace serveru - commands |
|
19 # - porty 17xx1-9 jsou pro long running tasks |
|
20 |
|
21 """ |
|
22 server pro určitý uživatelský kanál |
|
23 instance vytváří a spouští main.py |
|
24 """ |
|
25 class Server(): |
|
26 |
|
27 def __init__(self, d, chan): |
|
28 self._baseid = "{}.server[{:02d}]".format(d.debid, chan) |
|
29 self.d = D("{}".format(self._baseid)) |
|
30 |
|
31 Parms.clientMode = False |
|
32 Parms.sslCert = join(Parms.sslPath, "srv.pem") |
|
33 self.d.log("ssl path: {}".format(Parms.sslPath), sev=3) |
|
34 |
|
35 self._chan = chan |
|
36 self._homedir = join(Parms.srv_homedir, "{:02d}".format(self._chan)) |
|
37 os.chdir(self._homedir) |
|
38 self.d.log("working dir={}, ls: {}".format(os.getcwd(), os.listdir(), sev=4)) |
|
39 os.umask(0o077) |
|
40 |
|
41 self._filedir = join(self._homedir, Parms.filedir) |
|
42 |
|
43 try: |
|
44 self._node = Node(self.d, host='', chan=self._chan, conn=False, tryPort=False) |
|
45 except Exception as e: |
|
46 self.d.abend("creating network node", e) |
|
47 sys.exit(1) |
|
48 self.server() |
|
49 |
|
50 def server(self): |
|
51 # v této metodě server poslouchá na základním portu v kanálu (port 0); |
|
52 # po připojení klienta alokuje socket a synchronně cyklicky volá metodu service() k provedení příkazu klienta; |
|
53 # cyklus volání service() trvá dokud service() nevrátí False; |
|
54 # smyslem cyklu před novým acceptem je dát možnost klientu provést rychlou dávku příkazů proveditelných okamžitě; |
|
55 if not os.path.exists(Parms.clipfile): |
|
56 os.system("touch " + Parms.clipfile) |
|
57 if not os.path.exists(Parms.filedir): |
|
58 os.mkdir(Parms.filedir) |
|
59 while True: |
|
60 try: |
|
61 if self._node.acc(acc_TO=None): |
|
62 while self.service(): pass |
|
63 except KeyboardInterrupt: |
|
64 self.d.log("accept: KeyboardInterrupt") |
|
65 break |
|
66 if self.d.ll(4): self.d.log("closing ssc...") |
|
67 self._node.close_ssc() |
|
68 |
|
69 def service(self): |
|
70 # metoda synchronně ("blocking") dostává ze socketu příkaz klienta, provede ho |
|
71 # a vrací true, když chce nechat socket otevřený a false, když se může socket zavřít; |
|
72 # socket se nechává otevřený, když má smysl provádět danou operaci v dávce; |
|
73 # je na klientu, aby dávka byla krátká a aby zavřel neprodleně |
|
74 cmd = "" |
|
75 try: |
|
76 if self.d.ll(2): self.d.log("get command...") |
|
77 random.getrandbits(16) |
|
78 cmd = self._node.getcmd() # čtení osmi-znakového příkazu |
|
79 if self.d.ll(5): self.d.log("cmd len={}".format(len(cmd))) |
|
80 if not cmd: return False |
|
81 if self.d.ll(3): self.d.log("cmd={}".format(cmd)) |
|
82 # názvy operací pull/push odpovídají pohledu klienta; |
|
83 # operace jsou synchronní, blokují další příkazy v daném kanále a musí být v "lidském" měřítku krátké; |
|
84 # dlouhé operace se zahajují příkazem LONGTASK, po němž se alokuje paralelní nit k provedení dlouhé operace (typicky přenosu dat); |
|
85 if cmd == "PULLCLIP": # přenos posledního uloženého clipboardu na klienta |
|
86 return self.cmd_pullclip() |
|
87 elif cmd == "PULLHIST": # přenos přehledu uložených clipboard entries na klienta |
|
88 return self.cmd_pullhist() |
|
89 elif cmd == "PULLLIST": # přenos seznamu souborů v adresáři na klienta |
|
90 return self.cmd_pulllist() |
|
91 elif cmd == "PUSHCLIP": # příjem nového clipboardu k uložení na serveru |
|
92 return self.cmd_pushclip() |
|
93 elif cmd == "GETPEER": # předání uložené peer-adresy klientu |
|
94 return self.cmd_getpeer() |
|
95 elif cmd == "SETPEER": # příjem peer-adresy k uložení na serveru |
|
96 return self.cmd_setpeer() |
|
97 elif cmd == "MOVE": # přesun uloženého souboru/adresáře v rámci serveru |
|
98 return self.cmd_move() |
|
99 elif cmd == "EXPOSE": # vystavení souboru/adresáře na http-serveru - vrací se URI path |
|
100 return self.cmd_expose() |
|
101 elif cmd == "HIDE": # zakrytí souboru/adresáře pro http-server |
|
102 return self.cmd_hide() |
|
103 elif cmd == "DELETE": # rekurzivní výmaz souboru/adresáře na serveru |
|
104 return self.cmd_delete() |
|
105 elif cmd == "CREATDIR": # založení adresáře na serveru |
|
106 return self.cmd_createdir() |
|
107 elif cmd == "FREE": # předání informace o volném prostoru na serveru |
|
108 return self.cmd_freespace() |
|
109 elif cmd == "RECKON": # předání informace o velikost souboru/adresáře |
|
110 return self.cmd_reckon() |
|
111 elif cmd == "LONGTASK": # zahájení operace v jiném procesu na jiném portu |
|
112 return self.cmd_longtask() |
|
113 elif cmd == "HACK": |
|
114 self.d.log("hack") |
|
115 return False |
|
116 else: |
|
117 if self.d.ll(4): self.d.log("unknown command") |
|
118 self._node.close_sc() |
|
119 return False |
|
120 except KeyboardInterrupt: |
|
121 raise |
|
122 except Exception as e: |
|
123 # if self.d.ll(4): self.d.log("service {} aborted: {}: {}".format(cmd, e.__class__.__name__, str(e))) |
|
124 if self.d.ll(4): self.d.log("service {} aborted: {}".format(cmd, e)) |
|
125 traceback.print_tb(sys.exc_info()[2]) |
|
126 self._node.close_sc() |
|
127 return False |
|
128 |
|
129 def cmd_longtask(self): |
|
130 """ |
|
131 najde volný port 1-9, pošle ho klientovi, forkne se do paralelního procesu v němž |
|
132 čeká na připojení klienta, čte příkaz k operaci a provádí operaci |
|
133 - operace se nedávkují, na konci procesu se socket a serversocket zavírají a port uvolňuje |
|
134 - ssc, port = self._node.bindtrynext(Parms.srvhost) |
|
135 """ |
|
136 try: |
|
137 longtaskNode = Node(self.d, host='', chan=self._chan, tryPort=True, conn=False) |
|
138 except Node.AllPortsBusy: |
|
139 self._node.sendport(0) |
|
140 return False |
|
141 self._node.sendport(longtaskNode.port) |
|
142 self._node.close_sc() |
|
143 if os.fork(): |
|
144 return False |
|
145 # subprocess -------------- |
|
146 self._node = longtaskNode |
|
147 self.d.debid = "{} long task[{:d}]".format(self._baseid, self._node.port) |
|
148 random.seed() # je potřeba se odstřihnout od random-sekvence hlavního procesu |
|
149 try: |
|
150 if self._node.acc(acc_TO=Parms.long_run_accept_timeout): |
|
151 while self.longserv(): pass # cykluj, dokud je longserv positivní |
|
152 except Exception as e: |
|
153 self.d.abendMsg("long task accept", e=e) |
|
154 self._node.close_sc() |
|
155 self._node.close_ssc() |
|
156 os._exit(0) |
|
157 |
|
158 def longserv(self): |
|
159 cmd = self._node.getcmd() |
|
160 if not cmd: return False |
|
161 try: |
|
162 if self.d.ll(4): self.d.log("longtask cmd: " + cmd) |
|
163 # názvy operací pull/push odpovídají pohledu klienta |
|
164 if cmd == "PUSHFILE": # rekurzivní příjem souboru/adresáře k uložení na serveru |
|
165 return self.cmd_pushfile() |
|
166 elif cmd == "PUSHFIEX": # rekurzivní příjem souboru/adresáře k vystavení na http serveru |
|
167 return self.cmd_pushFileExpose() |
|
168 elif cmd == "PUSHSTEX": # příjem streamu k vystavení na http serveru |
|
169 return self.cmd_pushStreamExpose() |
|
170 elif cmd == "PULLFILE": # rekurzivní odeslání obsahu souboru/adresáře |
|
171 return self.cmd_pullfile() |
|
172 elif cmd == "PULLCLIP": # využije se pro dávkovou synchronizaci historie clipboardu |
|
173 return self.cmd_pullclip() |
|
174 elif cmd == "PULLHIST": # využije se pro dávkovou synchronizaci historie clipboardu |
|
175 return self.cmd_pullhist() |
|
176 elif cmd == "ECHO": |
|
177 return self.ee(cmd) |
|
178 elif cmd == "ECHOECHO": |
|
179 return self.ee(cmd) |
|
180 elif cmd == "HACK": |
|
181 return self.rand(6) |
|
182 else: |
|
183 return False |
|
184 except Exception as e: |
|
185 self.d.abendMsg("long task action {}".format(cmd), e=e) |
|
186 return False |
|
187 |
|
188 def rand(self, n): |
|
189 for i in range(n): |
|
190 self.d.log("{:04x}".format(random.getrandbits(16))) |
|
191 return False |
|
192 |
|
193 def ee(self, cmd): |
|
194 self.d.log("ECHO.004: cmd={}".format(cmd)) |
|
195 while cmd == "ECHO____" or cmd == "ECHOECHO": |
|
196 self.d.log("echoing...") |
|
197 self._node.putstr("ECHO") |
|
198 if cmd == "ECHOECHO": self.d.log("echo, got={}".format(self._node.getfn())) |
|
199 self.d.log("waiting for cmd") |
|
200 cmd = self._node.getcmd() |
|
201 self.d.log("cmd got={}".format(cmd)) |
|
202 self.d.log("waiting for close") |
|
203 self._node.get(1) # wait for other side close |
|
204 |
|
205 def cmd_pullfile(self): |
|
206 # operace download, v níž může klient dávkovat příkazy PULLFILE ke stažení objektů |
|
207 cmd = "PULLFILE" |
|
208 while cmd == "PULLFILE": |
|
209 req = self.formpath(self._node.getfn()) |
|
210 if req: |
|
211 fp = os.path.normpath(join(Parms.filedir, req)) |
|
212 if os.path.exists(fp): |
|
213 prefix = os.path.dirname(fp) |
|
214 if self.d.ll(3): self.d.log("pullfile request for '{}': starting...".format(fp)) |
|
215 self.pullfilerecurse(fp, prefix) |
|
216 else: |
|
217 if self.d.ll(3): self.d.log("pullfile request for '{}': not found".format(fp)) |
|
218 self._node.sendEOD() # end of recursive subtree of files |
|
219 cmd = self._node.getcmd() # another PULLFILE is expected here |
|
220 self._node.getcmd() # wait for other side close |
|
221 return False # konec dávky |
|
222 |
|
223 def pullfilerecurse(self, fp, prefix): |
|
224 if os.path.isdir(fp): |
|
225 for cwd, void, files in os.walk(fp, topdown=False): |
|
226 for entry in files: |
|
227 self.pullfilerecurse(join(cwd, entry), prefix) |
|
228 self._node.putfileinfo(cwd, os.path.relpath(cwd, start=prefix)) |
|
229 else: |
|
230 self._node.putfileinfo(fp, os.path.relpath(fp, start=prefix)) |
|
231 with open(fp, mode='rb') as f: |
|
232 g = self._node.genput() |
|
233 g.send(None) |
|
234 data = f.read(Parms.bufSize) |
|
235 while data: |
|
236 try: |
|
237 g.send(data) |
|
238 data = f.read(Parms.bufSize) |
|
239 except Exception: |
|
240 break |
|
241 g.close() |
|
242 if self.d.ll(3): self.d.log("PULLFILE: entry {} sent".format(fp)) |
|
243 |
|
244 def cmd_pushfile(self): |
|
245 # předpokládá se, že subadresáře, do nichž se přijímá, jsou vždycky writable, vznikly uploadem |
|
246 if self.d.ll(4): self.d.log("push file start") |
|
247 cmd = "PUSHFILE" |
|
248 while cmd == "PUSHFILE": |
|
249 req = self._node.getfn() # relativní cesta |
|
250 while len(req) > 0: # rekurzivní načtení stromu - končí prázdným req |
|
251 req = self.formpath(req) |
|
252 uploadPath = join(Parms.filedir, req) |
|
253 size = self._node.getnum() |
|
254 timestamp = self._node.getnum() |
|
255 if self.d.ll(4): self.d.log("push: req={} to=[{}, dir={}]...".format(req, uploadPath, size == -1)) |
|
256 if size < 0: |
|
257 self._node.receive_dir(uploadPath, size, timestamp) |
|
258 else: |
|
259 self._node.receive_file(uploadPath, size, timestamp) |
|
260 req = self._node.getfn() |
|
261 cmd = self._node.getcmd() # PUSHFILE or BATCHEND is expected here |
|
262 if self.d.ll(4): self.d.log("cmd_pushfile(), iterace v dávce, cmd={}".format(cmd)) |
|
263 self._node.putnum(0) # client awaits end of transfer confirmation |
|
264 if self.d.ll(4): self.d.log("push file finished") |
|
265 return False # konec dávky |
|
266 |
|
267 def cmd_pushFileExpose(self): |
|
268 """ |
|
269 Upload and Expose to HTTP |
|
270 ● klient posílá na pozadí objekt, který se vystaví na http-serveru |
|
271 ● objekt se ukládá do zvláštního adresáře self._exposed pod randomizovaným jménem |
|
272 ● klientovi se posílá segment "path" z výsledného URL, URL si zkomletuje klient |
|
273 ● metoda vrací False, protože se expose nedávkuje |
|
274 :return: False |
|
275 """ |
|
276 if self.d.ll(4): self.d.log("push and expose file start") |
|
277 self.link_exposed() |
|
278 req = self._node.getfn() # relativní cesta |
|
279 if len(req) > 0: |
|
280 stem, void, rest = self.formpath(req).partition('/') |
|
281 exposedPath = self.randomize_path(stem, self._exposed) |
|
282 while len(req) > 0: # rekurzivní načtení stromu - končí prázdným req |
|
283 uploadPath = join(self._exposed, exposedPath) |
|
284 void, void, rest = self.formpath(req).partition('/') |
|
285 if rest: uploadPath = join(uploadPath, rest) |
|
286 size = self._node.getnum() |
|
287 timestamp = self._node.getnum() |
|
288 if self.d.ll(4): self.d.log("push to '{}, dir={}'...".format(uploadPath, size == -1)) |
|
289 if size < 0: |
|
290 self._node.receive_dir(uploadPath, size, timestamp) |
|
291 else: |
|
292 self._node.receive_file(uploadPath, size, timestamp) |
|
293 req = self._node.getfn() # relativní cesta |
|
294 self.d.log("{} uploaded".format(exposedPath)) |
|
295 self.expose_link(exposedPath) |
|
296 if self.d.ll(4): self.d.log("push and expose file end") |
|
297 else: |
|
298 self.node.putnum(0) |
|
299 return False # konec, expose se nedávkuje |
|
300 |
|
301 def cmd_pushStreamExpose(self): |
|
302 """ |
|
303 Upload and Expose to HTTP |
|
304 ● klient posílá na pozadí stream, který se vystaví na http-serveru |
|
305 ● stream se ukládá do zvláštního adresáře self._exposed pod randomizovaným jménem |
|
306 ● klientovi se posílá segment "path" z výsledného URL, URL si zkomletuje klient |
|
307 ● metoda vrací False, protože se expose nedávkuje |
|
308 :return: False |
|
309 """ |
|
310 if self.d.ll(4): self.d.log("push and expose stream start") |
|
311 expName, origName = None, Node |
|
312 mimeType = self._node.getstr() |
|
313 size = self._node.getnum() |
|
314 if size > 0: |
|
315 self.link_exposed() |
|
316 (type, suffix) = mimeType.split('/') |
|
317 if not type or type == '*': type = "content" |
|
318 expName = self.randomize_path(type, self._exposed) |
|
319 if suffix and suffix != '*': expName += '.' + suffix |
|
320 expPath = join(self._exposed, expName) |
|
321 if self.d.ll(4): self.d.log("push stream to '{}'...".format(expPath)) |
|
322 self._node.receive_stream(expPath, size) |
|
323 # subprocess.run(('touch', expPath)) |
|
324 self.expose_link(expName) |
|
325 if self.d.ll(4): self.d.log("push stream to '{}' finished".format(expPath)) |
|
326 else: |
|
327 """self.node.putnum(0)""" |
|
328 return False # konec dávky |
|
329 |
|
330 def cmd_pulllist(self): |
|
331 req = self.formpath(self._node.getfn()) |
|
332 if req: |
|
333 fp = os.path.normpath(join(Parms.filedir, req)) |
|
334 if self.d.ll(4): self.d.log("dir=" + fp) |
|
335 if os.path.exists(fp) and os.access(fp, os.R_OK | os.X_OK): |
|
336 if os.path.isdir(fp): |
|
337 for entry in os.listdir(path=fp): |
|
338 self._node.putfileinfo(join(fp, entry), entry) |
|
339 else: |
|
340 self._node.putfileinfo(fp, req) |
|
341 self._node.putnum(0) |
|
342 self._node.close_sc() |
|
343 return False # konec dávky |
|
344 |
|
345 def cmd_pullclip(self): |
|
346 fn = self._node.getfn() |
|
347 if fn == "": |
|
348 fp = Parms.clipfile |
|
349 else: |
|
350 fp = Parms.histdir + "/" + fn |
|
351 if self.d.ll(4): |
|
352 self.d.log("clip fp={}".format(fp)) |
|
353 if os.path.exists(fp): |
|
354 self._node.putnum(os.path.getsize(fp)) |
|
355 with open(fp, mode="rb") as f: |
|
356 g = self._node.genput() |
|
357 g.send(None) |
|
358 data = f.read(Parms.bufSize) |
|
359 while len(data) > 0: |
|
360 try: |
|
361 g.send(data) |
|
362 data = f.read(Parms.bufSize) |
|
363 except Exception: |
|
364 break |
|
365 g.close() |
|
366 return True # případné dávkování |
|
367 |
|
368 def cmd_pushclip(self): |
|
369 with open(Parms.clipfile, mode='wb') as f: |
|
370 for data in self._node.genget(size = -1): |
|
371 f.write(data) |
|
372 self._node.close_sc() |
|
373 subprocess.call(("cp", "-a", Parms.clipfile, join(Parms.histdir, self.hist_fn()))) |
|
374 if self.d.ll(4): self.d.log("pushclip: {} bytes stored".format(os.path.getsize(Parms.clipfile))) |
|
375 return False # konec dávky |
|
376 |
|
377 def hist_fn(self): # random string file name |
|
378 if not os.path.exists(Parms.histdir): |
|
379 os.mkdir(Parms.histdir) |
|
380 a = (string.digits + string.ascii_letters) |
|
381 fn = "" |
|
382 for i in list(range(5)): |
|
383 fn += a[random.randint(0, 61)] |
|
384 while os.path.exists(join(Parms.histdir, fn)): |
|
385 fn = "" |
|
386 for i in list(range(5)): |
|
387 fn += a[random.randint(0, 61)] |
|
388 return fn |
|
389 |
|
390 def cmd_pullhist(self): |
|
391 if os.path.exists(Parms.histdir) and os.path.isdir(Parms.histdir): |
|
392 for entry in os.listdir(Parms.histdir): |
|
393 p = join(Parms.histdir, entry) |
|
394 self._node.putstr(entry) |
|
395 sample = open(p, mode="rb").read(80) # pošli vzorek max.80 z každého entry |
|
396 self._node.putnum(len(sample)) |
|
397 self._node.put(sample) |
|
398 self._node.putnum(os.path.getsize(p)) |
|
399 self._node.putnum(int(os.path.getmtime(p))) |
|
400 self._node.putnum(0) # konec streamu |
|
401 return True |
|
402 |
|
403 def cmd_expose(self): |
|
404 """ |
|
405 Expose to HTTP |
|
406 ● klient posílá jméno objektu na serveru, který se má vystavit na http-serveru |
|
407 ● jméno je cesta relativní k self._filedir |
|
408 ● objekt se symlinkuje ve zvláštním adresáři self._exposed randomizovaným jménem odvozeným ze jména objektu |
|
409 ● klientovi se posílá segment "path" z výsledného URL, URL si zkompletuje klient |
|
410 ● metoda vrací False, protože se expose nedávkuje |
|
411 :return: False |
|
412 """ |
|
413 fpath = join(self._filedir, self.formpath(self._node.getfn())) # node.getfn() je cesta relativně k self._filedir |
|
414 self.link_exposed() |
|
415 expName = self.randomize_path(os.path.basename(fpath), self._exposed) |
|
416 expPath = join(self._exposed, expName) |
|
417 subprocess.run(('ln', '-sfnr', fpath, expPath), check=True) |
|
418 if self.d.ll(3): self.d.log("fn={}".format(expPath)) |
|
419 self.expose_link(expName) |
|
420 return False # expose nemůže být v dávce, protože se klientovi posílá zpátky URI path |
|
421 |
|
422 def cmd_hide(self): |
|
423 fpath = self.formpath(self._node.getfn()) |
|
424 if self.d.ll(3): self.d.log("fn={}".format(fpath)) |
|
425 channel = "{:02d}".format(self._chan) |
|
426 fpath = join(Parms.srv_homedir, channel, Parms.filedir, fpath) |
|
427 subprocess.run(('chmod', '-R', 'o-rwx', fpath)) |
|
428 subprocess.run(('find', fpath, '-name', '.htaccess', '-exec', 'rm', '-f', '{}', '+')) |
|
429 return True # případné dávkování |
|
430 |
|
431 def cmd_getpeer(self): # atavismus z doby před použitím UDP broadcast |
|
432 host = "" |
|
433 port = 0 |
|
434 if len(self.peer) == 2: |
|
435 host = "10.0.2.2" if self.peer[0] == "10.0.2.15" else str(self.peer[0]) |
|
436 port = self.peer[1] |
|
437 self.peer = [] |
|
438 self._node.putstr(host) |
|
439 self._node.putnum(port) |
|
440 self._node.close_sc() |
|
441 return False |
|
442 |
|
443 def cmd_setpeer(self): # atavismus z doby před použitím UDP broadcast |
|
444 self.peer = (str(self._node.getfn()), int(self._node.getnum())) |
|
445 self._node.close_sc() |
|
446 return False |
|
447 |
|
448 def cmd_move(self): |
|
449 orig = self.formpath(self._node.getfn()) |
|
450 target = self.formpath(self._node.getfn()) |
|
451 if orig and target: |
|
452 if self.d.ll(4): self.d.log("performing mv -n {} {}".format(orig, target)) |
|
453 subprocess.call(["mv", "-n", join(Parms.filedir, orig), join(Parms.filedir, target)]) |
|
454 return True # případné dávkování |
|
455 |
|
456 def cmd_delete(self): |
|
457 req = self.formpath(self._node.getfn()) |
|
458 if req: |
|
459 if self.d.ll(4): self.d.log("performing rm -rf {}".format(req)) |
|
460 subprocess.call(["rm", "-rf", join(Parms.filedir, req)]) |
|
461 return True # případné dávkování |
|
462 |
|
463 def cmd_createdir(self): |
|
464 req = self.formpath(self._node.getfn()) |
|
465 if req: |
|
466 fp = join(Parms.filedir, req) |
|
467 if self.d.ll(4): self.d.log("performing mkdir -p {}".format(fp)) |
|
468 subprocess.call(["mkdir", "-p", fp]) |
|
469 return True # případné dávkování |
|
470 |
|
471 def cmd_freespace(self): |
|
472 # df v Debianu 7.6 nemá parametr --output :-( |
|
473 p = subprocess.Popen(["df", "--block-size=1", "."], stdout=subprocess.PIPE) |
|
474 p.wait() |
|
475 if p.returncode == 0: |
|
476 free = int(p.stdout.readlines()[1].decode().split()[3]) |
|
477 else: |
|
478 free = -1 |
|
479 self._node.putnum(free) |
|
480 self._node.close_sc() |
|
481 return True # případné dávkování |
|
482 |
|
483 def cmd_reckon(self): |
|
484 req = self.formpath(self._node.getfn()) |
|
485 self.sendobjectsize(join(Parms.filedir, req) if req else None) |
|
486 return True # případné dávkování |
|
487 |
|
488 def sendobjectsize(self, fp): |
|
489 if not fp or not os.path.exists(fp): |
|
490 size, fnum, dnum = (0, 0, 0) |
|
491 elif os.path.isfile(fp): |
|
492 size, fnum, dnum = (os.path.getsize(fp), 1, 0) |
|
493 else: |
|
494 size, fnum, dnum = self.dirsize(fp) |
|
495 self._node.putnum(int(size)) |
|
496 self._node.putnum(int(fnum)) |
|
497 self._node.putnum(int(dnum)) |
|
498 |
|
499 def link_exposed(self): |
|
500 """ |
|
501 ● založení adresáře pro vystavené objekty self._exposed podle Parms.exposed |
|
502 ● adresář je symlinkován z http-serveru číslem kanálu |
|
503 ● symlink z http-serveru musí vytvořit instalace nebo super-user |
|
504 ● na http-serveru se při instalaci aplikace zakládá adresář pro tuto aplikaci se symlinky na exposed dirs jednotlivých kanálů |
|
505 ● <http_server>/<appl_name>/<kanál> --> <appl_home>/<kanál>/<files>/<exposed> |
|
506 ● <http_server>/<appl_name> musí mít povoleny symlinky a povolen( instalovat .htaccess s Options Indexes |
|
507 ● http-server musí mít x-access po cestě <http_server>/<appl_name>/<kanál> --> <exposed> |
|
508 ● výšeuvedené atributy se nemůže zařídit aplikace, musí být nastaveny při instalaci nebo administrátorem |
|
509 """ |
|
510 self._exposed = join(self._filedir, Parms.exposed) |
|
511 if not os.path.exists(self._exposed): |
|
512 subprocess.run(('mkdir', '-pm771', self._exposed)) |
|
513 # os.mkdir(self._exposed, mode=0o771) |
|
514 channel = "{:02d}".format(self._chan) |
|
515 self._wwwhome = join(Parms.srv_wwwhomedir, channel) |
|
516 if not os.path.exists(self._wwwhome): |
|
517 subprocess.run(('ln', '-sfnr', self._exposed, self._wwwhome), check=True) |
|
518 |
|
519 def expose_link(self, path): |
|
520 """ |
|
521 ● fpath je cesta relativní k self._exposed |
|
522 ● úkolem je zařídit read-access k filům, x-access k dirs, případně .htacess v kořenu stromu |
|
523 """ |
|
524 orig = join(self._exposed, path) |
|
525 self.d.log("orig={}".format(orig), sev=4) |
|
526 try: |
|
527 subprocess.run(('chmod', 'o+r', orig), check=True) |
|
528 if os.path.isdir(orig): |
|
529 self.expose_dir_tree(orig) |
|
530 elif path.find('/') > 0: |
|
531 self.expose_dir_path(path) |
|
532 channel = "{:02d}".format(self._chan) |
|
533 # uriPath = urllib.parse.quote(join(Parms.applName, channel, path)) |
|
534 uriPath = join(Parms.applName, channel, path) |
|
535 self._node.putstr(uriPath) # pošli URL-path klientovi |
|
536 self.d.log("uri path {} sent".format(uriPath), sev=3) |
|
537 except Exception as e: |
|
538 self.d.abend("exposing file to web server", e) |
|
539 |
|
540 def expose_dir_path(self, rel_file_path): |
|
541 """ |
|
542 po cestě k vystavenému souboru je potřeba nastavit x-access pro http-server |
|
543 :param rel_file_path: cesta relativní k self._exposed |
|
544 """ |
|
545 base = self._exposed |
|
546 while rel_file_path: |
|
547 subprocess.run(('chmod', 'o=x', base)) |
|
548 (subdir, sep, rel_file_path) = rel_file_path.partition('/') |
|
549 base = join(base, subdir) |
|
550 |
|
551 def expose_dir_tree(self, rel_dir_path): |
|
552 """ |
|
553 do vystaveného stromu je třeba umístit .htaccess, po cestě nastavit x-access, ve stromu nastavit rx-access |
|
554 :param rel_dir_path: cesta relativní k self._exposed |
|
555 """ |
|
556 htaccess = join(rel_dir_path, ".htaccess") |
|
557 with open(htaccess, mode="w") as f: |
|
558 f.write("Options Indexes") |
|
559 subprocess.run(('chmod', 'o+r', htaccess)) |
|
560 subprocess.run(('chmod', 'o+x', rel_dir_path)) |
|
561 subprocess.run(('chmod', '-R', 'o+r', rel_dir_path)) |
|
562 for (rel_dir_path, dirs, files) in os.walk(rel_dir_path): |
|
563 for dir in dirs: |
|
564 dpath = join(rel_dir_path, dir) |
|
565 subprocess.run(('chmod', 'o+x', dpath)) |
|
566 |
|
567 def randomize_path(self, fn, dirname): |
|
568 name, void, suff = os.path.basename(fn).rpartition(".") |
|
569 expName = "" |
|
570 while os.path.exists(join(dirname, expName)) or expName == "": |
|
571 uniq = "{:04x}".format(random.getrandbits(16)) |
|
572 if name: |
|
573 expName = name + "." + uniq + "." + suff |
|
574 else: |
|
575 expName = suff + "." + uniq |
|
576 return expName |
|
577 |
|
578 def dirsize(self, fp): |
|
579 if self.d.ll(4): self.d.log("performing du -sb '{}'".format(fp)) |
|
580 try: |
|
581 size = subprocess.check_output(["du", "-sb", fp])[:-1].decode().split("\t")[0] |
|
582 fnum = len(subprocess.check_output(["find", fp, "-type", "f"]).split(b'\n'))-1 |
|
583 dnum = len(subprocess.check_output(["find", fp, "-type", "d"]).split(b'\n'))-1 |
|
584 except subprocess.CalledProcessError: |
|
585 (size, fnum, dnum) = (-1, 0, 0) |
|
586 if self.d.ll(5): self.d.log("size={}, type={}, fnum={}. type={}, dnum={}, type={}" |
|
587 .format(size, type(size), fnum, type(fnum), dnum, type(dnum))) |
|
588 return (size, fnum, dnum) |
|
589 |
|
590 def close_sc(self): |
|
591 self._node.close_sc() |
|
592 |
|
593 def formpath(self, path): |
|
594 """ |
|
595 - v žádném případě absolutní cesta |
|
596 - vrací None, když path jde up from current |
|
597 """ |
|
598 if path.startswith('/'): path = path[1:] |
|
599 p = os.path.normpath(path) |
|
600 return None if p == '..' or p.startswith('../') else p |