http://kassiopeia.juls.savba.sk/~garabik/software/python-utmp garabik @ kassiopeia.juls.savba.sk ==================================================== python-utmp consists of three modules, providing access to utmp records. It is quite difficult to access utmp record portably, because every UNIX has different structure of utmp files. Currently, python-utmp works on platforms which provide getutent, getutid, getutline, pututline, setutent, endutent and utmpname functions (such as GNU systems (Linux and hurd) and System V unices) and on BSD systems using simple utmp structure. python-utmp is known to work on linux with glibc2.0, glibc2.1, glibc2.2, glibc2.3; SunOS 5.6; HP-UX B.10.20; NetBSD 1.4.2; OpenBSD 2.9 Python 3.X support ------------------ Starting from version 0.8, there is now a partial python3 support. In particular, ut_line, ut_id, ut_user, ut_host are strings, not bytes. If any of these fields contain non-ASCII characters, utmpaccess module will try to convert them into strings using the default system encoding (usually utf-8). If the field happens to contain invalid utf-8 sequences, this will raise an exception. However, this should not happen under normal circumstances – strings in utmp should be ASCII only, so this indicates a pathological situation, probably a damage to the utmp file. Correct solution would be for utmpaccess to return bytes, and let utmp.py deal with it, by indicating that we want either bytes or strings. Some of the examples work with python3, converting others will be trivial. There was a big API change in version 0.7 – old interface has been kept for backward portability, but may be removed in the future. Usually, system provides functions for accessing utmp, but on *BSD systems the only way (IMHO) is to read utmp file as consisting of C-structures. It is difficult to unite both worlds. To make things worse, some OS'es (hint: SunOS :-)) use utmpx in parallel to utmp, utmpx containing more information. python-utmp provides getutent() & comp. as an emulation layer if native system is lacking these functions, providing ideal glibc-like utmp structure, emulating fields present in this structure but missing at the specific platform. Missing member: Default used instead: ut_type USER_PROCESS DEAD_PROCESS if ut_name and ut_host are empty EMPTY if ut_name, ut_host and ut_line are empty BOOT_TIME if ut_line is "~" ut_pid 0 ut_id ut_line ut_host '' ut_exit (0, 0) ut_tv.tv_sec ut_time ut_tv.tv_usec 0 ut_addr_v6 4*[0] ut_session 0 CAVEAT: If the native platform misses ut_type, it distinguishes between user processes and pseudo-entries by ut_line. python-utmp tries to guess the appropriate ut_type in this case. It might not guess correctly. Moreover, I have seen some quirks with ut_type==USER_PROCESS for date entries with linux. Therefore, date entries (with ut_line=="{" or ut_line=="|") are considered to be user processes for the time being. Similarly, if you write into utmp and your ut_type is not USER_PROCESS, corresponding ut_host and ut_user are cleared (=filled with zeros). Depending on your system, this may or may not be the right thing to do. It is correct at least with NetBSD. Reading from utmp and wtmp should be safe and bulletproof. Module "utmpaccess" provides low level access to utmp structure, in most cases you should use module "utmp" instead. utmpaccess provides following functions (see getutent(3) and utmp(5) for more information): ut_addr_v6 is a tuple of 4 integers, ut_exit is tuple of (e_termination, e_exit) ut_tv is tuple of (tv_sec, tv_usec) getutent(): returns None if there is an error (e.g. going past the end of utmp file) otherwise returns tuple of: (ut_type, ut_pid, ut_line, ut_id, ut_user, ut_host, ut_exit, ut_session, ut_tv, ut_addr_v6) getutid(ut_type[, ut_id]): returns None if there is an error (e.g. not found), otherwise returns tuple of: (ut_type, ut_pid, ut_line, ut_id, ut_user, ut_host, ut_exit, ut_session, ut_tv, ut_addr_v6) getutline(ut_line): returns None if there is an error (e.g. not found), otherwise returns tuple of: (ut_type, ut_pid, ut_line, ut_id, ut_user, ut_host, ut_exit, ut_session, ut_tv, ut_addr_v6) pututline(ut_type, ut_pid, ut_line, ut_id, ut_user, ut_host, ut_e_termination, ut_exit, ut_session, ut_tv, ut_addr_v6): returns None setutent(): returns None endutent() returns None utmpname(fname): returns None UTMPCONST.py provides convenient constants for work with utmpaccess.py, taken from system constants, or some sensibly made-up values if system does not provide them. (see UTMPCONST.py source) These constants are provided: EMPTY RUN_LVL BOOT_TIME NEW_TIME OLD_TIME INIT_PROCESS LOGIN_PROCESS USER_PROCESS DEAD_PROCESS ACCOUNTING UT_UNKNOWN UT_LINESIZE UT_NAMESIZE UT_HOSTSIZE UT_IDSIZE UTMP_FILE WTMP_FILE UT_IDSIZE if the size of ut_id (usually 4). If your system does not have ut_id, ut_id is simulated by ut_line, and UT_IDSIZE==UT_LINESIZE Module "utmp" provides class UtmpEntry, correcponding to one entry in UTMP/WTMP file. UtmpEntry has following attributes (values after "=" demonstrate default values) ut_type = EMPTY ut_pid = 0 ut_line = '' ut_id = '' ut_user = '' ut_host = '' ut_exit = (0, 0) ut_session = 0 ut_tv = (0, 0) ut_addr_v6 = (0, 0, 0, 0) You can initialize new UtmpEntry via several different ways: - passing a tuple to the constructor: >>> tpl = (USER_PROCESS, 1234, 'tty5', 'id', 'root', 'localhost', (0, 0), 0, (0, 0), (0, 0, 0, 0)) >>> entry1 = UtmpEntry(tpl) - using tuple as arguments: >>> entry2 = UtmpEntry(USER_PROCESS, 1234, 'tty5', 'id', 'root', 'localhost', (0, 0), 0, (0, 0), (0, 0, 0, 0)) - using dictionary >>> dic = { 'ut_type':USER_PROCESS, 'ut_pid':1234, 'ut_line':'tty5', 'ut_user':'root' } >>> entry3 = UtmpEntry(dic) - using named parameters: >>> entry4 = UtmpEntry(ut_type=USER_PROCESS, ut_pid=1234, ut_line='tty5', ut_user='root') Omitted parameters get the default values. - using already existing instance of UtmpEntry >>> entry5 = UtmpEntry(entry4) The preferred way is to get UtmpEntry from an existing UtmRecord, modify it according to your needs and write it back. UtmpEntry supports tuple- and dictionary-like interface, for compatibility with previous versions of python-utmp. So you can use entry5[0] or entry5['ut_type'] instead of entry5.ut_type. This will be probably removed in the future. Module "utmp" also provides class UtmpRecord with following methods: getutent() getutid(ut_type[, ut_id]): getutline(ut_line) pututline(UtmpEntry) for backward compatibility, you can use the same parameters as when creating UtmpEntry instance. setutent() endutent() old *_dict() methods are still kept for backward compatibility; they may be removed in the future. as of version 0.6, it also provides an iterator which enables you to write loops like this (it requires python>2.2): u = utmp.UtmpRecord() for i in u: # i is an UtmpEntry instance, as if returned by u.getutent() You pass filename for utmpname() when creating instance of the class, e.g. a=Utmp.UtmpRecord("/var/log/wtmp")