### uuid.py -- pure python implementation of uuidgen ### ### Copyright (C) 2005 Denys Duchier ### ### This library is free software; you can redistribute it and/or ### modify it under the terms of the GNU Lesser General Public ### License as published by the Free Software Foundation; either ### version 2.1 of the License, or (at your option) any later version. ### ### This library is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ### Lesser General Public License for more details. ### ### You should have received a copy of the GNU Lesser General Public ### License along with this library; if not, write to the Free Software ### Foundation, Inc., 51 Franklin ST, Fifth Floor, Boston, MA 02110-1301 USA ### ################################################################################ ### ### The code below follows, as closely as I could manage, Theodore ### Tso's C code of libuuid's file gen_uuid.c distributed with ### package e2fsprogs ### ### enabling "psyco" considerably improves the performance of this code ### ### public API: ### ### uuid.generate() ### uuid.generate_random() ### uuid.generate_time() ### ### uuid.linux_generate() ### ### uuid.py_generate() ### uuid.py_generate_random() ### uuid.py_generate_time() ### ### uuid.set_method(USE_MAC|USE_SHA|USE_RANDOM) ### ### these functions return a new uuid string on each invocation. ### ################################################################################ """\ This module provides functions to generate universally unique identifiers (UUID). It is provides an implementation in pure python that follows closely Theodore's Tso's libuuid C code distributed with the e2fsprogs package. It is also able to use the system's libuuid if available. The following functions use the system's libuuid if available, or the Linux /proc/sys/kernel/random/uuid interface if available, else default back to the pure python implementation: generate() -- generate a uuid using the best available method generate_random() -- generate a uuid using a random number generator generate_time() -- generate a uuid based on time and hardware address Pure python implementation: py_generate() -- like generate() but in pure python py_generate_random() -- like generate_random() but in pure python py_generate_time() -- like generate_time but in pure python a uuid is a string that looks like fa485e1d-3b1d-413b-aac0-72aaf2830ec0, i.e. follows the printf format %08x-%04x-%04x-%04x-%012x. Note: py_generate_time is much faster than py_generate_random but of much lesser quality Hint: enabling psyco considerably improves the performance of the pure python implementation. """ __all__ = ["generate","generate_random","generate_time", "py_generate","py_generate_random","py_generate_time", "set_method","USE_MAC","USE_RANDOM","USE_SHA"] import os import datetime from time import mktime as _mktime from random import randint as _randint from struct import pack as _pack, unpack as _unpack from sys import maxint as _maxint _now = datetime.datetime.now def _gettimeofday(): d=_now() return int(_mktime(d.timetuple())),d.microsecond _random_reader = None _random_init = False if hasattr(os,"urandom"): try: os.urandom(8) _random_reader = os.urandom _random_init = True except: pass def _get_random_reader(): global _random_reader, _random_init if not _random_init: try: import random try: _random_reader = open("/dev/urandom","rb").read except: _random_reader = os.fdopen(os.open("/dev/random",os.O_RDONLY|os.O_NONBLOCK),"rb").read sec,usec = _gettimeofday() random.seed((os.getpid() << 16) ^ os.getuid() ^ sec ^ usec) except: pass _random_init = True sec,usec = _gettimeofday() i = (sec ^ usec) & 0x1F while i>0: _randint(0,_maxint) i -= 1 return _random_reader _fmt_16 = ">16B" _fmt_6 = ">6B" _fmt_2 = ">2B" def _randomize_byte(b): return b ^ ((_randint(0,_maxint) >> 7) & 0xFF) def _get_random_bytes(n): reader = _get_random_reader() buf = '' if reader: loose_counter = 0 while len(buf) != n: buf += reader(n) if loose_counter > 10: break loose_counter += 1 d = n-len(buf) if d>0: buf += '\0'*d if n==16: fmt = _fmt_16 elif n==6: fmt = _fmt_6 elif n==2: fmt = _fmt_2 else: fmt = ">%sB" % n return _pack(fmt,*tuple(map(_randomize_byte,_unpack(fmt,buf)))) def _uuid_unpack(buf): return _unpack(">IHHH6s",buf) def _uuid_unpack_fully(buf): return _unpack(">IHHHBBBBBB",buf) def _uuid_pack(low,mid,hi,seq,node): return _pack(">IHHH6s",low,mid,hi,seq,node) def _uuid_unparse(uu): low,mid,hi,seq,b5,b4,b3,b2,b1,b0 = _uuid_unpack_fully(uu) return "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % (low,mid,hi,seq>>8,seq&0xFF,b5,b4,b3,b2,b1,b0) def _uuid_generate_random(): buf = _get_random_bytes(16) low,mid,hi_and_version,seq,node = _uuid_unpack(buf) seq = (seq & 0x3FFF) | 0x8000 hi_and_version = (hi_and_version & 0x0FFF) | 0x4000 return _uuid_pack(low,mid,hi_and_version,seq,node) def _backtick(pgm): try: i,o = os.popen4(pgm) i.close() return o.read() except: return "" def _decode_hex(s): return int(s,16) def _get_mac_address(): import re r = re.compile("HWaddr (..:..:..:..:..:..)") m = r.search(_backtick("/sbin/ifconfig")) if m: s = m.group(1) return map(_decode_hex,s.split(":")) m = r.search(_backtick("ifconfig")) if m: s = m.group(1) return map(_decode_hex,s.split(":")) r = re.compile("Physical Address.*: (..:..:..:..:..:..)") m = r.search(_backtick("ipconfig /all")) if m: s = m.group(1) return map(_decode_hex,s.split(":")) return None def _get_6bytes(mac=None): try: import sha,getpass,socket,time buf = sha.new() if mac: buf.update("%c%c%c%c%c%c " % tuple(mac)) buf.update(getpass.getuser()) buf.update("@%s" % socket.gethostname()) buf.update(" %s" % os.getpid()) buf.update(" %s" % time.strftime("%Y-%m-%d %H:%M:%S %Z")) return map(ord,buf.digest()[0:6]) except: return None USE_MAC = 0 USE_RANDOM = 1 USE_SHA = 2 _common_suffix_method = USE_MAC _static_time_buf = None def _set_common_suffix(): global _static_time_buf if _common_suffix_method == USE_RANDOM: _static_time_buf = _get_random_bytes(6) # I don't know how to set the multicast bit # so I am leaving that out else: suf = _get_mac_address() if _common_suffix_method == USE_SHA: suf = _get_6bytes(suf) if suf: _static_time_buf = _pack(_fmt_6,*suf) else: _static_time_buf = _get_random_bytes(6) # I don't know how to set the multicast bit # so I am leaving that out def set_method(meth): """Set the method to be used to generate the common suffix of uuids when py_generate_time is invoked. The argument can be one of the following constrants: USE_MAC -- use the mac address is it can be determined, else try USE_SHA USE_SHA -- create a digest of mac address, user info and time, else try USE_RANDOM USE_RANDOM -- generate a random number """ global _common_suffix_method if _common_suffix_method == meth: return _common_suffix_method = meth _set_common_suffix() def _uuid_generate_time(): global _static_time_buf if not _static_time_buf: _set_common_suffix() mid,low,seq = _get_clock() seq = seq | 0x8000 hi = (mid >> 16) | 0x1000 return _uuid_pack(low,mid,hi,seq,_static_time_buf) _static_last_sec = 0 _static_last_msec = 0 _static_adjustment = 0 _MAX_ADJUSTMENT = 10 _static_clock_seq = 0 def _get_clock(): global _static_last_sec,_static_last_msec,_static_adjustment,_static_clock_seq sec,msec = _gettimeofday() try_again = True while try_again: try_again = False if _static_last_sec==0 and _static_last_msec==0: _static_clock_seq = _unpack(">H",_get_random_bytes(2))[0] & 0x1FFF _static_last_sec = sec - 1 _static_last_msec = msec if sec < _static_last_sec or ((sec == _static_last_sec) and (msec < _static_last_msec)): _static_clock_seq = (_static_clock_seq+1) & 0x1FFF _static_adjustment = 0 _static_last_sec = sec _static_last_msec = msec elif sec == _static_last_sec and msec == _static_last_msec: if _static_adjustment >= _MAX_ADJUSTMENT: try_again = True else: _static_adjustment += 1 else: _static_adjustment = 0 _static_last_sec = sec _static_last_msec = msec clock_reg = msec*10 + _static_adjustment clock_reg = (clock_reg + sec*10000000) & 0xFFFFFFFFFFFFFFFFL clock_reg = (clock_reg + (((0x01B21DD2L) << 32) + 0x13814000L)) & 0xFFFFFFFFFFFFFFFFL return (clock_reg >> 32),(clock_reg & 0xFFFFFFFFL),_static_clock_seq def _uuid_generate(): if _get_random_reader(): return _uuid_generate_random() else: return _uuid_generate_time() def py_generate(): """Generate a UUID using the best available method. uses py_generate_random if a high-quality source of randomness is available, else py_generate_time """ return _uuid_unparse(_uuid_generate()) def py_generate_time(): """Generate a UUID by mixing time and MAC address. """ return _uuid_unparse(_uuid_generate_time()) def py_generate_random(): """Generate a UUID using a high-quality source of randomness. """ return _uuid_unparse(_uuid_generate_random()) ################################################################################ ### Here is an interface to libuuid, just in case it happens to be available ### ################################################################################ _libuuid = None try: import dl _libuuid = dl.open("libuuid.so") except: pass # I am being a bad boy: libuuid functions, when invoked, are passed strings # which they modify. This is supposed to be a no-no as documented in the dl # module. However, I create these strings anew each time using struct.pack, # they are never uniquified nor hashed, and they are thrown away as soon as # possible. I think this is actually safe. If I am wrong, please let me know. def libuuid_generate(): """Generate a UUID with libuuid using the best available method. This will raise an exception if libuuid is not available. """ buf = _pack(">16s","") out = _pack(">37s","") _libuuid.call("uuid_generate",buf) _libuuid.call("uuid_unparse",buf,out) return _unpack(">36sB",out)[0] def libuuid_generate_random(): """Generate a UUID with libuuid using a high-quality source of randomness. This will raise an exception if libuuid is not available. """ buf = _pack(">16s","") out = _pack(">37s","") _libuuid.call("uuid_generate_random",buf) _libuuid.call("uuid_unparse",buf,out) return _unpack(">36sB",out)[0] def libuuid_generate_time(): """Generate a UUID with libuuid by mixing time and MAC address. This will raise an exception if libuuid is not available. """ buf = _pack(">16s","") out = _pack(">37s","") _libuuid.call("uuid_generate_time",buf) _libuuid.call("uuid_unparse",buf,out) return _unpack(">36sB",out)[0] def linux_generate(): """Generate a UUID by reading from /proc/sys/kernel/random/uuid. This will raise an exception if we are not on Linux """ f = open('/proc/sys/kernel/random/uuid') s = f.readline().strip() f.close() return s generate = None generate_random = None generate_time = None if _libuuid: generate = libuuid_generate generate_random = libuuid_generate_random generate_time = libuuid_generate_time else: try: if linux_generate(): generate = linux_generate generate_random = linux_generate generate_time = linux_generate except: pass if not generate: generate = py_generate generate_random = py_generate_random generate_time = py_generate_time if __name__ == '__main__': import getopt,sys gen = generate opts,args = getopt.getopt(sys.argv[1:],"rth",["help"]) for o,v in opts: if o == "-t": gen = generate_time elif o == "-r": gen = generate_random else: print "usage: uuid.py [-r|-t]" print "generate a random-based UUID (-r, default), or a time-based one (-t)" sys.exit(0) print gen()