• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / zdaemon / 12825577578

17 Jan 2025 08:41AM UTC coverage: 79.914% (-0.08%) from 79.992%
12825577578

Pull #37

github

web-flow
Trigger CI
Pull Request #37: Update Python version support.

361 of 512 branches covered (70.51%)

Branch coverage included in aggregate %.

5 of 15 new or added lines in 5 files covered. (33.33%)

5 existing lines in 4 files now uncovered.

1676 of 2037 relevant lines covered (82.28%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.03
/src/zdaemon/zdctl.py
1
#!python
2
##############################################################################
3
#
4
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
5
# All Rights Reserved.
6
#
7
# This software is subject to the provisions of the Zope Public License,
8
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
9
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
10
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
12
# FOR A PARTICULAR PURPOSE.
13
#
14
##############################################################################
15
"""zdctl -- control an application run by zdaemon.
16

17
Usage: python zdctl.py [-C URL] [-S schema.xml] [-h] [-p PROGRAM]
18
       [zdrun-options] [action [arguments]]
19

20
Options:
21
-b/--backoff-limit SECONDS -- set backoff limit to SECONDS (default 10)
22
-C/--configure URL -- configuration file or URL
23
-d/--daemon -- run as a proper daemon; fork a subprocess, close files etc.
24
-f/--forever -- run forever (by default, exit when backoff limit is exceeded)
25
-h/--help -- print this usage message and exit
26
-t/--transcript FILE -- log file where to redirect stdout and stderr
27
-l/--logfile -- log file to be read by logtail command
28
-p/--program PROGRAM -- the program to run
29
-S/--schema XML Schema -- XML schema for configuration file
30
-T/--start-timeout SECONDS -- Start timeout when a test program is used
31
-s/--socket-name SOCKET -- Unix socket name for client (default "zdsock")
32
-u/--user USER -- run as this user (or numeric uid)
33
-m/--umask UMASK -- use this umask for daemon subprocess (default is 022)
34
-x/--exit-codes LIST -- list of fatal exit codes (default "0,2")
35
--version -- print zdaemon version and exit
36
-z/--directory DIRECTORY -- directory to chdir to when using -d (default off)
37
action [arguments] -- see below
38

39
Actions are commands like "start", "stop" and "status".  Use the
40
action "help" to find out about available actions.
41
"""
42

43
import cmd
1✔
44
import os
1✔
45
import os.path
1✔
46
import re
1✔
47
import signal
1✔
48
import socket
1✔
49
import stat
1✔
50
import sys
1✔
51
import time
1✔
52

53

54
if __name__ == "__main__":
1!
55
    # Add the parent of the script directory to the module search path
56
    # (but only when the script is run from inside the zdaemon package)
57
    from os.path import abspath
×
58
    from os.path import basename
×
59
    from os.path import dirname
×
60
    from os.path import normpath
×
61
    scriptdir = dirname(normpath(abspath(sys.argv[0])))
×
62
    if basename(scriptdir).lower() == "zdaemon":
×
63
        sys.path.append(dirname(scriptdir))
×
64
    here = os.path.dirname(os.path.realpath(__file__))
×
65
    swhome = os.path.dirname(here)
×
66
    for parts in [("src",), ("lib", "python"), ("Lib", "site-packages")]:
×
67
        d = os.path.join(swhome, *(parts + ("zdaemon",)))
×
68
        if os.path.isdir(d):
×
69
            d = os.path.join(swhome, *parts)
×
70
            sys.path.insert(0, d)
×
71
            break
×
72

73
from zdaemon.zdoptions import RunnerOptions
1✔
74
from zdaemon.zdoptions import name2signal
1✔
75

76

77
def string_list(arg):
1✔
78
    return arg.split()
1✔
79

80

81
class ZDCtlOptions(RunnerOptions):
1✔
82

83
    __doc__ = __doc__
1✔
84

85
    positional_args_allowed = True
1✔
86

87
    def __init__(self):
1✔
88
        RunnerOptions.__init__(self)
1✔
89
        self.add("schemafile", short="S:", long="schema=",
1✔
90
                 default="schema.xml",
91
                 handler=self.set_schemafile)
92
        self.add("program", "runner.program", "p:", "program=",
1✔
93
                 handler=string_list,
94
                 required="no program specified; use -p or -C")
95
        self.add("logfile", "runner.logfile", "l:", "logfile=")
1✔
96
        self.add("start_timeout", "runner.start_timeout",
1✔
97
                 "T:", "start-timeout=", int, default=300)
98
        self.add("python", "runner.python")
1✔
99
        self.add("zdrun", "runner.zdrun")
1✔
100
        programname = os.path.basename(sys.argv[0])
1✔
101
        base, ext = os.path.splitext(programname)
1✔
102
        if ext == ".py":
1✔
103
            programname = base
1✔
104
        self.add("prompt", "runner.prompt", default=(programname + ">"))
1✔
105

106
    def realize(self, *args, **kwds):
1✔
107

108
        RunnerOptions.realize(self, *args, **kwds)
1✔
109

110
        # Maybe the config file requires -i or positional args
111
        if not self.args:
1!
112
            self.usage("an action argument is required")
×
113

114
        # Where's python?
115
        if not self.python:
1!
116
            self.python = sys.executable
1✔
117

118
    def set_schemafile(self, file):
1✔
119
        self.schemafile = file
×
120

121

122
class ZDCmd(cmd.Cmd):
1✔
123

124
    def __init__(self, options):
1✔
125
        self.options = options
1✔
126
        self.prompt = self.options.prompt + ' '
1✔
127
        cmd.Cmd.__init__(self)
1✔
128
        self.get_status()
1✔
129
        if self.zd_status:
1✔
130
            m = re.search("(?m)^args=(.*)$", self.zd_status)
1✔
131
            if m:
1!
132
                s = m.group(1)
1✔
133
                args = eval(s, {"__builtins__": {}})
1✔
134
                program = self.options.program
1✔
135
                if args[:len(program)] != program:
1!
136
                    print("WARNING! zdrun is managing a different program!")
×
137
                    print("our program   =", program)
×
138
                    print("daemon's args =", args)
×
139

140
        if options.configroot is not None:
1✔
141
            env = getattr(options.configroot, 'environment', None)
1✔
142
            if env is not None:
1✔
143
                if getattr(env, 'mapping', None) is not None:
1!
144
                    for k, v in env.mapping.items():
1✔
145
                        os.environ[k] = v
1✔
146
                elif isinstance(env, dict):
×
147
                    for k, v in env.items():
×
148
                        os.environ[k] = v
×
149

150
        self.create_rundir()
1✔
151
        self.create_socket_dir()
1✔
152
        self.set_uid()
1✔
153

154
    def create_rundir(self):
1✔
155
        if self.options.directory is None:
1✔
156
            return
1✔
157
        self.create_directory(self.options.directory)
1✔
158

159
    def create_socket_dir(self):
1✔
160
        dir = os.path.dirname(self.options.sockname)
1✔
161
        if not dir:
1✔
162
            return
1✔
163
        self.create_directory(dir)
1✔
164

165
    def create_directory(self, directory):
1✔
166
        if os.path.isdir(directory):
1✔
167
            return
1✔
168
        os.mkdir(directory)
1✔
169
        uid = os.geteuid()
1✔
170
        if uid == 0 and uid != self.options.uid:
1✔
171
            # Change owner of directory to target
172
            os.chown(directory, self.options.uid, self.options.gid)
1✔
173

174
    def set_uid(self):
1✔
175
        user = self.options.user
1✔
176
        if user is None:
1✔
177
            return
1✔
178

179
        import pwd
1✔
180
        try:
1✔
181
            uid = int(user)
1✔
182
        except ValueError:
1✔
183
            try:
1✔
184
                pwrec = pwd.getpwnam(user)
1✔
185
            except KeyError:
×
186
                self.options.usage("username %r not found" % user)
×
187
            uid = pwrec.pw_uid
1✔
188
        else:
189
            try:
×
190
                pwrec = pwd.getpwuid(uid)
×
191
            except KeyError:
×
192
                self.options.usage("uid %r not found" % user)
×
193

194
        # See if we're already that user:
195
        euid = os.geteuid()
1✔
196
        if euid != 0:
1✔
197
            if euid != uid:
1✔
198
                self.options.usage("only root can use -u USER to change users")
1✔
199
            return
1✔
200

201
        # OK, we have to set user and groups:
202
        os.setgid(pwrec.pw_gid)
1✔
203

204
        import grp
1✔
205
        user = pwrec.pw_name
1✔
206
        os.setgroups(
1✔
207
            sorted(g.gr_gid for g in grp.getgrall()  # sort for tests
208
                   if user in g.gr_mem)
209
        )
210
        os.setuid(uid)
1✔
211

212
    def emptyline(self):
1✔
213
        # We don't want a blank line to repeat the last command.
214
        # Showing status is a nice alternative.
215
        self.do_status()
×
216

217
    def send_action(self, action):
1✔
218
        """Send an action to the zdrun server and return the response.
219

220
        Return None if the server is not up or any other error happened.
221
        """
222
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1✔
223
        try:
1✔
224
            sock.connect(self.options.sockname)
1✔
225
            sock.send(action.encode() + b"\n")
1✔
226
            sock.shutdown(1)  # We're not writing any more
1✔
227
            response = b""
1✔
228
            while True:
1✔
229
                data = sock.recv(1000)
1✔
230
                if not data:
1✔
231
                    break
1✔
232
                response += data
1✔
233
            return response.decode()
1✔
234
        except OSError:
1✔
235
            return None
1✔
236
        finally:
237
            sock.close()
1✔
238

239
    zd_testing = 0
1✔
240

241
    def get_status(self):
1✔
242
        self.zd_up = 0
1✔
243
        self.zd_pid = 0
1✔
244
        self.zd_should_be_up = 0
1✔
245
        self.zd_status = None
1✔
246
        resp = self.send_action("status")
1✔
247
        if not resp:
1✔
248
            return resp
1✔
249
        m = re.search(r"(?m)^application=(\d+)$", resp)
1✔
250
        if not m:
1!
251
            return resp
×
252
        self.zd_up = 1
1✔
253
        self.zd_pid = int(m.group(1))
1✔
254
        self.zd_status = resp
1✔
255
        m = re.search(r"(?m)^should_be_up=(\d+)$", resp)
1✔
256
        if m:
1!
257
            self.zd_should_be_up = int(m.group(1))
1✔
258
        else:
259
            self.zd_should_be_up = 1
×
260
        m = re.search(r"(?m)^testing=(\d+)$", resp)
1✔
261
        if m:
1!
262
            self.zd_testing = int(m.group(1))
1✔
263
        else:
264
            self.zd_testing = 0
×
265

266
        return resp
1✔
267

268
    def awhile(self, cond, msg):
1✔
269
        n = 0
1✔
270
        was_running = False
1✔
271
        try:
1✔
272
            if self.get_status():
1✔
273
                was_running = True
1✔
274

275
            while not cond(n):
1✔
276
                sys.stdout.write(". ")
1✔
277
                sys.stdout.flush()
1✔
278
                time.sleep(1)
1✔
279
                n += 1
1✔
280
                if self.get_status():
1✔
281
                    was_running = True
1✔
282
                elif (was_running or n > 10) and not cond(n):
1✔
283
                    print("\ndaemon manager not running")
1✔
284
                    return 1
1✔
285

286
        except KeyboardInterrupt:
1✔
287
            print("^C")
×
288
        print("\n" + msg % self.__dict__)
1✔
289

290
    def _start_cond(self, n):
1✔
291
        if (n > self.options.start_timeout):
1✔
292
            print('\nProgram took too long to start')
1✔
293
            sys.exit(1)
1✔
294
        return self.zd_pid and not self.zd_testing
1✔
295

296
    def do_start(self, arg):
1✔
297
        self.get_status()
1✔
298
        if not self.zd_up:
1!
299
            if self.options.zdrun:
1!
300
                args = [self.options.python, self.options.zdrun]
×
301
            else:
302
                args = [self.options.python, sys.argv[0]]
1✔
303
                os.environ['DAEMON_MANAGER_MODE'] = '1'
1✔
304

305
            args += self._get_override("-S", "schemafile")
1✔
306
            args += self._get_override("-C", "configfile")
1✔
307
            args += self._get_override("-b", "backofflimit")
1✔
308
            args += self._get_override("-f", "forever", flag=1)
1✔
309
            args += self._get_override("-s", "sockname")
1✔
310
            args += self._get_override("-u", "user")
1✔
311
            args += self._get_override("-t", "transcript")
1✔
312
            if self.options.umask:
1!
313
                args += self._get_override("-m", "umask",
1✔
314
                                           oct(self.options.umask))
315
            args += self._get_override(
1✔
316
                "-x", "exitcodes", ",".join(map(str, self.options.exitcodes)))
317
            args += self._get_override("-z", "directory")
1✔
318
            args.extend(self.options.program)
1✔
319
            args.extend(self.options.args[1:])
1✔
320
            if self.options.daemon:
1✔
321
                flag = os.P_NOWAIT
1✔
322
            else:
323
                flag = os.P_WAIT
1✔
324
            os.spawnvp(flag, args[0], args)
1✔
325
        elif not self.zd_pid:
×
326
            self.send_action("start")
×
327
        else:
328
            print("daemon process already running; pid=%d" % self.zd_pid)
×
329
            return
×
330
        if self.options.daemon:
1✔
331
            return self.awhile(
1✔
332
                self._start_cond, "daemon process started, pid=%(zd_pid)d")
333

334
    def _get_override(self, opt, name, svalue=None, flag=0):
1✔
335
        value = getattr(self.options, name)
1✔
336
        if value is None:
1✔
337
            return []
1✔
338
        configroot = self.options.configroot
1✔
339
        if configroot is not None:
1✔
340
            for n, cn in self.options.names_list:
1✔
341
                if n == name and cn:
1✔
342
                    v = configroot
1✔
343
                    for p in cn.split("."):
1✔
344
                        v = getattr(v, p, None)
1✔
345
                        if v is None:
1✔
346
                            break
1✔
347
                    if v == value:  # We didn't override anything
1✔
348
                        return []
1✔
349
                    break
1✔
350
        if flag:
1✔
351
            if value:
1!
352
                args = [opt]
×
353
            else:
354
                args = []
1✔
355
        else:
356
            if svalue is None:
1✔
357
                svalue = str(value)
1✔
358
            args = [opt, svalue]
1✔
359
        return args
1✔
360

361
    def help_start(self):
1✔
362
        print("start -- Start the daemon process.")
1✔
363
        print("         If it is already running, do nothing.")
1✔
364

365
    def do_stop(self, arg):
1✔
366
        self.get_status()
1✔
367
        if not self.zd_up:
1!
368
            print("daemon manager not running")
×
369
        elif not self.zd_pid and not self.zd_should_be_up:
1!
370
            print("daemon process not running")
×
371
        else:
372
            self.send_action("stop")
1✔
373
            self.awhile(lambda n: not self.zd_pid, "daemon process stopped")
1✔
374

375
    def help_stop(self):
1✔
376
        print("stop -- Stop the daemon process.")
1✔
377
        print("        If it is not running, do nothing.")
1✔
378

379
    def do_reopen_transcript(self, arg):
1✔
380
        if not self.zd_up:
1!
381
            print("daemon manager not running")
×
382
        else:
383
            self.send_action("reopen_transcript")
1✔
384

385
    def help_reopen_transcript(self):
1✔
386
        print("reopen_transcript -- Reopen the transcript log file.")
1✔
387
        print("                     Use after log rotation.")
1✔
388

389
    def do_restart(self, arg):
1✔
390
        self.get_status()
1✔
391
        pid = self.zd_pid
1✔
392
        if not pid:
1!
393
            self.do_start(arg)
×
394
        else:
395
            self.send_action("restart")
1✔
396
            self.awhile(lambda n: (self.zd_pid != pid) and self._start_cond(n),
1✔
397
                        "daemon process restarted, pid=%(zd_pid)d")
398

399
    def help_restart(self):
1✔
400
        print("restart -- Stop and then start the daemon process.")
1✔
401

402
    def do_kill(self, arg):
1✔
403
        if not arg:
1✔
404
            arg = 'SIGTERM'
1✔
405
        try:
1✔
406
            signame = name2signal(arg)
1✔
407
        except ValueError:
1✔
408
            print("invalid signal", repr(arg))
1✔
409
            return
1✔
410
        self.get_status()
1✔
411
        if not self.zd_pid:
1✔
412
            print("daemon process not running")
1✔
413
            return
1✔
414
        sig = getattr(signal, signame)
1✔
415
        print("kill(%d, %d)" % (self.zd_pid, sig))
1✔
416
        try:
1✔
417
            os.kill(self.zd_pid, sig)
1✔
NEW
418
        except OSError as msg:
×
419
            print("Error:", msg)
×
420
        else:
421
            print("signal %s sent to process %d" % (signame, self.zd_pid))
1✔
422

423
    def help_kill(self):
1✔
424
        print("kill [sig] -- Send signal sig to the daemon process.")
1✔
425
        print("              The default signal is SIGTERM.")
1✔
426

427
    def do_wait(self, arg):
1✔
428
        self.awhile(lambda n: not self.zd_pid, "daemon process stopped")
×
429
        self.do_status()
×
430

431
    def help_wait(self):
1✔
432
        print("wait -- Wait for the daemon process to exit.")
1✔
433

434
    def do_status(self, arg=""):
1✔
435
        status = 0
1✔
436
        if arg not in ["", "-l"]:
1!
437
            print("status argument must be absent or -l")
×
438
            return 1
×
439
        self.get_status()
1✔
440
        if not self.zd_up:
1✔
441
            print("daemon manager not running")
1✔
442
            status = 3
1✔
443
        elif not self.zd_pid:
1!
444
            print("daemon manager running; daemon process not running")
×
445
        else:
446
            print("program running; pid=%d" % self.zd_pid)
1✔
447
        if arg == "-l" and self.zd_status:
1!
448
            print(self.zd_status)
×
449
        return status
1✔
450

451
    def help_status(self):
1✔
452
        print("status [-l] -- Print status for the daemon process.")
1✔
453
        print("               With -l, show raw status output as well.")
1✔
454

455
    def do_show(self, arg):
1✔
456
        if not arg:
×
457
            arg = "options"
×
458
        try:
×
459
            method = getattr(self, "show_" + arg)
×
460
        except AttributeError as err:
×
461
            print(err)
×
462
            self.help_show()
×
463
            return
×
464
        method()
×
465

466
    def show_options(self):
1✔
467
        print("zdctl/zdrun options:")
×
468
        print("schemafile:  ", repr(self.options.schemafile))
×
469
        print("configfile:  ", repr(self.options.configfile))
×
470
        print("zdrun:       ", repr(self.options.zdrun))
×
471
        print("python:      ", repr(self.options.python))
×
472
        print("program:     ", repr(self.options.program))
×
473
        print("backofflimit:", repr(self.options.backofflimit))
×
474
        print("daemon:      ", repr(self.options.daemon))
×
475
        print("forever:     ", repr(self.options.forever))
×
476
        print("sockname:    ", repr(self.options.sockname))
×
477
        print("exitcodes:   ", repr(self.options.exitcodes))
×
478
        print("user:        ", repr(self.options.user))
×
479
        umask = self.options.umask
×
480
        if not umask:
×
481
            # Here we're just getting the current umask so we can report it:
482
            umask = os.umask(0o777)
×
483
            os.umask(umask)
×
484
        print("umask:       ", oct(umask))
×
485
        print("directory:   ", repr(self.options.directory))
×
486
        print("logfile:     ", repr(self.options.logfile))
×
487
        print("transcript:  ", repr(self.options.transcript))
×
488

489
    def show_python(self):
1✔
490
        print("Python info:")
×
491
        version = sys.version.replace("\n", "\n              ")
×
492
        print("Version:     ", version)
×
493
        print("Platform:    ", sys.platform)
×
494
        print("Executable:  ", repr(sys.executable))
×
495
        print("Arguments:   ", repr(sys.argv))
×
496
        print("Directory:   ", repr(os.getcwd()))
×
497
        print("Path:")
×
498
        for dir in sys.path:
×
499
            print("    " + repr(dir))
×
500

501
    def show_all(self):
1✔
502
        self.show_options()
×
503
        print()
×
504
        self.show_python()
×
505

506
    def help_show(self):
1✔
507
        print("show options -- show zdctl options")
1✔
508
        print("show python -- show Python version and details")
1✔
509
        print("show all -- show all of the above")
1✔
510

511
    def do_logreopen(self, arg):
1✔
512
        self.do_reopen_transcript('')
1✔
513
        self.do_kill('USR2')
1✔
514

515
    def help_logreopen(self):
1✔
516
        print("logreopen -- Send a SIGUSR2 signal to the daemon process.")
1✔
517
        print("             This is designed to reopen the log file.")
1✔
518
        print("             Also reopens the transcript log file.")
1✔
519

520
    def do_logtail(self, arg):
1✔
521
        if not arg:
×
522
            arg = self.options.logfile
×
523
            if not arg:
×
524
                print("No default log file specified; use logtail <logfile>")
×
525
                return
×
526
        try:
×
527
            helper = TailHelper(arg)
×
528
            helper.tailf()
×
529
        except KeyboardInterrupt:
×
530
            print()
×
531
        except OSError as msg:
×
532
            print(msg)
×
533
        except OSError as msg:
×
534
            print(msg)
×
535

536
    def help_logtail(self):
1✔
537
        print("logtail [logfile] -- Run tail -f on the given logfile.")
1✔
538
        print("                     A default file may exist.")
1✔
539
        print("                     Hit ^C to exit this mode.")
1✔
540

541
    def do_foreground(self, arg):
1✔
542
        self.get_status()
1✔
543
        pid = self.zd_pid
1✔
544
        if pid:
1!
545
            print(
×
546
                "To run the program in the foreground, please stop it first.")
547
            return
×
548

549
        program = self.options.program + self.options.args[1:]
1✔
550
        print(" ".join(program))
1✔
551
        sys.stdout.flush()
1✔
552
        try:
1✔
553
            os.spawnlp(os.P_WAIT, program[0], *program)
1✔
554
        except KeyboardInterrupt:
×
555
            print()
×
556

557
    def do_fg(self, arg):
1✔
558
        self.do_foreground(arg)
1✔
559

560
    def help_foreground(self):
1✔
561
        print("foreground -- Run the program in the forground.")
1✔
562
        print("fg -- an alias for foreground.")
1✔
563

564
    def help_fg(self):
1✔
565
        self.help_foreground()
1✔
566

567
    def help_help(self):
1✔
568
        print("help          -- Print a list of available actions.")
1✔
569
        print("help <action> -- Print help for <action>.")
1✔
570

571

572
class TailHelper:
1✔
573

574
    MAX_BUFFSIZE = 1024
1✔
575

576
    def __init__(self, fname):
1✔
577
        self.f = open(fname)
×
578

579
    def tailf(self):
1✔
580
        sz, lines = self.tail(10)
×
581
        for line in lines:
×
582
            sys.stdout.write(line)
×
583
            sys.stdout.flush()
×
UNCOV
584
        while True:
×
585
            newsz = self.fsize()
×
586
            bytes_added = newsz - sz
×
587
            if bytes_added < 0:
×
588
                sz = 0
×
589
                print("==> File truncated <==")
×
590
                bytes_added = newsz
×
591
            if bytes_added > 0:
×
592
                self.f.seek(-bytes_added, 2)
×
593
                bytes = self.f.read(bytes_added)
×
594
                sys.stdout.write(bytes)
×
595
                sys.stdout.flush()
×
596
                sz = newsz
×
597
            time.sleep(1)
×
598

599
    def tail(self, max=10):
1✔
600
        self.f.seek(0, 2)
×
601
        pos = sz = self.f.tell()
×
602

603
        lines = []
×
604
        bytes = []
×
605
        num_bytes = 0
×
606

UNCOV
607
        while True:
×
608
            if pos == 0:
×
609
                break
×
610
            self.f.seek(pos)
×
611
            byte = self.f.read(1)
×
612
            if byte == '\n':
×
613
                if len(lines) == max:
×
614
                    break
×
615
                bytes.reverse()
×
616
                line = ''.join(bytes)
×
617
                line and lines.append(line)
×
618
                bytes = []
×
619
            bytes.append(byte)
×
620
            num_bytes = num_bytes + 1
×
621
            if num_bytes > self.MAX_BUFFSIZE:
×
622
                break
×
623
            pos = pos - 1
×
624
        lines.reverse()
×
625
        return sz, lines
×
626

627
    def fsize(self):
1✔
628
        return os.fstat(self.f.fileno())[stat.ST_SIZE]
×
629

630

631
def main(args=None, options=None, cmdclass=ZDCmd):
1✔
632
    if args is None:
1✔
633
        args = sys.argv[1:]
1✔
634

635
    if os.environ.get('DAEMON_MANAGER_MODE'):
1✔
636
        del os.environ['DAEMON_MANAGER_MODE']
1✔
637
        import zdaemon.zdrun
1✔
638
        return zdaemon.zdrun.main(args)
1✔
639

640
    if options is None:
1!
641
        options = ZDCtlOptions()
1✔
642
    options.realize(args)
1✔
643
    c = cmdclass(options)
1✔
644
    sys.exit(c.onecmd(" ".join(options.args)))
1✔
645

646

647
if __name__ == '__main__':
648
    main()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc