# uuid.py -- pure python implementation of uuidgen
#
# Copyright (C) 2005 Denys Duchier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
###
################################################################################
###
### 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.
###
################################################################################
from __future__ import absolute_import
"""\
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
from random import randint
from struct import pack
from struct import unpack
from sys import 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()
|