「사기열전」중에서..

하규의 책공은 이렇게 말했다. 처음 내가 정위가 되었을 때는 빈객이 문 앞에 가득 찼지만, 파면되자 문밖에 참새 잡는 그물을 쳐도 될 정도였다. 내가 다시 정위가 되자 빈객들은 예전처럼 모여들려고 했다. 그래서 나는 문에 이렇게 크게 써서 붙였다.

한 번 죽고 한 번 사는데 사귀는 정을 알고, 한 번 가난하고 한 번 부유함으로써 사귀는 모습을 알며, 한 번 귀했다가 한 번 천해짐으로써 사귀는 참된 정을 알게 된다.

“사기열전”中에서

(박카스를 좋아하시는 [WWW]ddt님의 홈페이지에서 재인용)

ktrace(1)를 이용한 자동 plist 생성

포트를 만들거나 업글할 때 가장 귀찮은 과정 중의 하나인 plist를 자동으로 만드는 방법은 [WWW]mtree기반으로 된 것이 있었지만, 뭔가 다른 프리픽스로 한 번 깔아보는 게 대략 귀찮은 관계로.. [FreeBSDMan]ktrace 를 이용한 방법을 한 번 생각해 봤습니다. 흐흐흐.. open 시스템 콜을 모두 추적해서 O_WRONLY나 O_CREAT로 열린 녀석들 목록을 자동으로 plist로 만들어주는 것인데, 대충 만들어 본 소스는..

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
164 
165 
166 
167 
168 
169 
170 
171 
172 
173 
174 
175 
176 
177 
178 
179 
180 
181 
182 
183 
184 
185 
186 
187 
188 
189 
#!/usr/bin/env python
#
# autoplist.py - Automatic pkg-plist generator based on ktrace
#
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42, (c) Poul-Henning Kamp):
# Hye-Shik Chang <perky@FreeBSD.org> wrote this file.  As long as you retain
# this notice you can do whatever you want with this stuff. If we meet some
# day, and you think this stuff is worth it, you can buy me a beer in return.
#
# Hye-Shik Chang
# ----------------------------------------------------------------------------
# $FreeBSD$

import os, random
import sys, re
import time

random.whseed()
TMPNAM = '.autoplist.%d.%x' % (os.getpid(), random.randrange(65536))
KTRACECMD = 'ktrace -f %s -id -t cn %%s' % TMPNAM
KDUMPCMD = 'kdump -f %s' % TMPNAM
O_WFLAGS = os.O_WRONLY | os.O_RDWR | os.O_CREAT
PREFIX = '/usr/local'
IGNOREPREFIX = ['/dev/', '/tmp/']
PLIST_SUBS = (
    'PYTHON_INCLUDEDIR', 'PYTHON_LIBDIR', 'PYTHON_SITELIBDIR',
    'SITE_PERL', 'SITE_RUBY', 'DOCSDIR', 'EXAMPLESDIR', 'DATADIR',
)
PLISTREPLACE = []

def errorexit(msg):
    print >> sys.stdout, "autoplist.py:", msg
    sys.exit(-1)

def cleanup_tmpfiles():
    try:
        os.unlink(TMPNAM)
    except:
        pass

def getmakeenv(envname):
    return os.popen('make -V "%s"' % envname).read().strip()

def echo_msg(msg):
    print '===>  ', msg

class InstallState:

    procs = {}
    lastforker = 0

    def __init__(self, pid, procname):
        self.pid = pid
        self.procname = procname
        if not self.lastforker:
            self.curdir = os.getcwd()
        else:
            self.curdir = self.procs[self.lastforker].curdir
        self.procs[pid] = self
        self.files, self.dirs = {}, {}
        self.trackatom = {}

    def feed(self, args):
        if self.trackatom:
            if args[2] == 'NAMI':
                self.trackatom['nami'].append(eval(args[3]))
            elif args[2] == 'RET':
                rtok = args[3].split()
                if rtok[1].isdigit() or rtok[1].startswith('0x'):
                    self.trackatom['exit'] = eval(rtok[1])
                else:
                    self.trackatom['exit'] = rtok[1]
                if len(rtok) >= 3:
                    self.trackatom['errno'] = eval(rtok[3])
                self.newsyscall(**self.trackatom)
                self.trackatom = {}
        elif args[2] == 'CALL':
            syscall, ignore, args = re_syscallargs.findall(args[3])[0]
            self.trackatom['syscall'] = syscall
            self.trackatom['args'] = args and args.split(',') or []
            self.trackatom['nami'] = []
            if syscall in ('fork', 'vfork', 'rfork'):
                InstallState.lastforker = self.pid

    def getabspath(self, path):
        if path.startswith('/'):
            return path
        else:
            return os.path.realpath(os.sep.join([self.curdir, path]))

    def newsyscall(self, syscall, args, nami, exit, errno=0):
        if syscall == 'open':
            if eval(args[1]) & O_WFLAGS:
                self.files[self.getabspath(nami[0])] = None
        elif syscall == 'mkdir':
            self.dirs[self.getabspath(nami[0])] = None
        elif syscall == 'chdir' and exit == 0:
            self.curdir = self.getabspath(nami[0])
        elif syscall in ('execve',) and exit == 0:
            self.procname = nami[0]

def filterfilelist(files):
    filelist = []
    warnlist = []
    prefix = PREFIX + os.sep
    for f in files:
        for pfx in IGNOREPREFIX:
            if f.startswith(pfx):
                continue
        if f.startswith(prefix):
            filelist.append(f[len(prefix):])
            for var, repl in PLISTREPLACE:
                if filelist[-1].startswith(repl):
                    filelist[-1] = filelist[-1].replace(repl, '%%'+var+'%%')
        else:
            warnlist.append(f)
    return filelist, warnlist

re_syscallargs = re.compile('([A-Za-z0-9_-]+)(\(([^)]*)\))?')
def getwfiles():
    entry = {}
    istate = InstallState.procs
    echo_msg('Analyzing installation syscall logs')
    for l in os.popen(KDUMPCMD):
        ltok = l[:-1].split(None, 3)
        pid = int(ltok[0])
        if not istate.has_key(pid):
            proc = istate[pid] = InstallState(pid, ltok[1])
        else:
            proc = istate[pid]
        proc.feed(ltok)

    echo_msg('Generating auto plist')
    fo = open("pkg-plist.autogen", "w")
    print >>fo, "@comment Generated by autoplist.py", time.asctime()

    files = {}
    dirs = {}
    for pid, data in istate.iteritems():
        files.update(data.files)
        dirs.update(data.dirs)

    files = files.keys()
    files.sort()
    files, warnfiles = filterfilelist(files)
    for file in files:
        print >>fo, file

    dirs = dirs.keys()
    dirs.sort()
    dirs.reverse()
    dirs, warndirs = filterfilelist(dirs)
    for dir in dirs:
        print >>fo, "@dirrm", dir

    return istate

def main():
    try:
        err = os.system('make build')
        if err != 0:
            errorexit("`make' exited with error code %d" % err)
        err = os.system(KTRACECMD % 'make install')
        if err != 0:
            errorexit("`make' exited with error code %d" % err)
        getwfiles()
    finally:
        cleanup_tmpfiles()

def buildenvironment():
    global IGNOREPREFIX, PREFIX, PLISTREPLACE

    IGNOREPREFIX.append(os.path.abspath(getmakeenv('WRKDIRPREFIX')))
    IGNOREPREFIX.append(os.path.abspath(getmakeenv('DISTDIR')))
    IGNOREPREFIX.append(os.path.abspath(getmakeenv('PKG_DBDIR')))
    PREFIX = getmakeenv('PREFIX')

    for var, raw, stripped in re.findall('([A-Za-z_0-9]+)=("([^"]*)"|[^"]\S*)',
                                getmakeenv('PLIST_SUB')):
        if var in PLIST_SUBS:
            PLISTREPLACE.append((var, stripped or raw))
    PLISTREPLACE.sort(lambda x,y: -cmp(x[1], y[1]))

if __name__ == '__main__':
    buildenvironment()
    main()

# ex: ts=9 sts=4 sw=4 et

흐흐.. 그런데, 대충 만들고 나서 보니, 속도가 너무 느리고 (좀만 대형 포트로 가면 엄청난 양의 kdump가 나와버려서 그것 파싱하는데 십분이 넘게 걸립니다 –;) mtree기반의 녀석에게 특별한 장점이 없다는 결론이.. 흐흐흐… (그리고, 위 구현에서는 디렉토리를 인스톨 도중에 mv 해 버린다던지 하는 것에 대해서 무방비 –;)

그래서, 이 녀석을 좀 변모시켜서 plistlint 정도의 이름으로 인스톨하면서 plist에 없는 프로그램 건드리지는 않나, 빼먹은 파일 없나, /tmp에 이상한 파일 남기지 않나 등등을 검사하는 걸로 만들어 볼 까 합니당. 크크

파이썬 2.3의 공유 라이브러리 지원

이미 해본 분들은 아시다시피 ~.~, 파이썬 2.3에서는 –enable-shared를 주면 공유 라이브러리로 사용이 가능하도록 돼서, libpython2.3.so가 1메가짜리가 나오고 python은 3천 바이트짜리가 나옵니다.

그래서, 이야 멋지다 하고 메모리 절약에 도움이 되지 않을까 하는 생각에, FreeBSD포트 [FreshPorts]lang/python 에는 덥썩 shared를 디폴트로 해 버렸는데, 다행히도 python-dev 메일링에 누가 “왜 –enable-shared가 디폴트가 아닌가요”하는 질문에 Martin v. Löwis가 좋은 대답을 해 주어서, 덕분에 잘못 알고 있었던 것을 바로 잡게 되었습니다. 으흐흐 (다들 알던 것인가;;)

웬지 공유 안 할 것 같아 보였던, static 바이너리도 inode가 같은 경우에는, text 이미지를 메모리에서 모두 공유한다는군요 (두둥!) ~.~;;

Löwis의 가르침(!)을 요약하면..

  • 파이썬 바이너리가 1개만 있으면, 걔네들은 모두 공유라이브러리 만큼 전부 메모리나 디스크 공간을 공유한다. 그런데, 거의 대부분의 파이썬 애플리케이션들이 파이썬 바이너리를 따로 쓰는 게 아니기 때문에, 대부분의 파이썬 애플리케이션들은 실행 파일 이미지를 메모리에서 공유한다고 볼 수 있다. 특별한 예외라면 [WWW]mod_python이 있는데, mod_python은 아파치 바이너리를 통해서 공유돼서, 다른 파이썬 바이너리와는 공유하지 않지만, 아파치 바이너리들 끼리 공유해서 같은 효과를 얻는다.

  • 공유 라이브러리를 유지하는 것은 상당한 관리상 문제점이 따른다. 어떤 경우에는 인스톨이 제대로 안 될 수도 있고, 공유라이브러리 링커에 따라 못 찾게 될 수도 있다.

  • 공유 라이브러리를 사용하면, 시작하는 시간이 길어진다. 또한, [WWW]ld.so에 디렉토리가 몇 개 더 추가되어야 하고, 빌트인 모듈로 컴파일되지 않은 파이썬 모듈까지 모두 공유라이브러리 패스에 들어가야 한다. (퍼키 주: 이 부분은 적어도 FreeBSD에서는 사실이 아닙니다. FreeBSD에서는 그냥 [WWW]dlopen(3)으로 절대경로찾도록 되기 때문에, 정적 컴파일한 것이나 같습니다.)

  • 공유 라이브러리를 사용하면, 실행 시 효율성이 떨어진다. PIC (Position-Independent-Code)는 일반적으로 non-PIC 바이너리보다 최적화에서 불리하다. ([WWW]블루님 주: 현실적으로는 거의 차이가 없다고 합니다. 😉

  • 공유 라이브러리를 들고 있으면, 관리 비용이 더 많이 든다. 컴파일러, 링커, 다이내믹 링커 등등 거의 모든 개발 툴에서 기존에 없었던 문제가 발생될 수 있고, 이에 대해서 일일이 고려해야한다.

  • 흐흐흐. 그래서, FreeBSD의 [FreshPorts]lang/python 포트도 별도의 공유 라이브러리 포트를 분리해 내고, 정적 컴파일로 다시 돌리려고 합니다. 만세 \(-.-)/

    (Martin v. Löwis의 원문은 [WWW]http://mail.python.org/pipermail/python-dev/2003-August/037472.html)

    4-STABLE의 PAE 지원

    FreeBSD의 물리적 주소 범위를 4GB이상으로 넓히는 Jake Burkholder의 PAE 옵션이 Luoqi Chen의 포팅 작업으로 4.x에 성공적으로 포팅돼서 그동안 많은 테스트가 있었는데, 이제 이번 주 금요일에 PAE가 4에 임포트가 된다고 합니다. PAE를 사용하는 드라이버같은 것들이 따로 아직 많은 편은 아니기 때문에 직접적인 영향은 없으리라 생각되지만, 그래도 안정적인 시스템 운용이 필요한 분들은 다음 주까지는 좀 미루시는 것이 좋겠군요. :)

    PAE가 들어오면 이제 FreeBSD 4에서도 4GB이상의 메모리를 쓸 수 있고, 2테라 이상의 단일 스토리지를 쓸 수 있게 됩니다. :) 그리고, 이것을 포함한 다음 릴리즈는 9월 예정인 FreeBSD 4.9! ~.~

    FreeBSD의 python2.3에서 py-xml문제

    python2.3의 _xmlplus 최소 버전이 0.8.2로 되어있어서, FreeBSD 포트 [FreshPorts]textproc/py-xml 으로는 python2.3에서 쓸 수 없었던 문제가 있었습니다. (사실은 그 문제 때문에, openlook도 잠시 cgitb 판이 됐었;; ~.~) 메인테이너인 wjv가 지금 직장을 옮기느라 바쁜 상태라서, 그냥 방금 0.8.3으로 업그레이드해 버렸습니다. 혹시 파이썬 2.3에서 py-xml이나 pyblosxom쓰시는 분은 이제 업글하시면 제대로 될 겁니다. ;)

    나비!

    그동안 ami가 특별한 경쟁자없이 주도하고 있던 XIM 세계에, 드디어 경쟁자가 나타났습니다. 바로 오픈소스 소리바다 클라이언트인 [WWW]소리받아와 GTK+2용 한글 입력기인 [WWW]imhangul로 유명하신 크리스나님께서 ‘나비’라는 이름의 예쁜 입력기를 공개하신 것!

    ‘나비’는 GTK+2기반으로 되어있지만 표준 XIM 인터페이스를 제공하기 때문에 OpenOffice나 KDE, xterm같은 곳에서도 쓸 수 있기에, ami나 hanIM같은 기존 XIM들을 완전히 대체할 수 있을 뿐더러, imhangul에서 보아왔던 유니코드 기반 오토마타를 내장하고 있기 때문에, 옛 한글 지원 같은 진보적인 기능들을 지원할 수 있는 기반을 갖추고 있습니다.

    또한, KDE와 GNOME에 도킹이 되는데, ami가 KDE3에 도킹이 제대로 안 되고 있었던 점을 감안하면 (ami는 KDE3보다 나중에 떠야지만 도킹이 됨) KDE사용자들에게도 이제 축복이~~ :) 그러나, NumLock 이 켜져있는 상태에서는 한글이 입력 안 된다거나, xterm에서 한글입력 중에 스페이스를 누르면 스페이스가 먼저 커밋되는 것 같은 사소한 몇가지 문제점이 있는데, 아직 0.1이기 때문에 개선의 여지는 충분히 많이 있겠죠~

    FreeBSD 포트 [FreshPorts]korean/nabi 에 등록되어 있습니다.

    파이썬 richrepr 아이디어

    지난 번에 string repr과 string print를 로켈에 맞게 수정한 버젼이 pickle같은 몇 가지 곳에서 문제가 발견되어 CVS에 들어갔다가 빠졌는데, 새로운 방법을 여러 모로 생각해 봤지만, 적당한 방법이 생각이 안 나서, 결국은 type object에 tp_richrepr 이라는 메쏘드를 새로 만드는 방법을 한 번 생각해 봤습니다.

    파이썬에서 오브젝트가 출력되는 방법은 두 가지인데,

    • PyFile_WriteObject에서 출력하는 오브젝트가 진짜 파일인 경우에는 PyObject_Print로 직접 FILE *fp에다가 출력. PyObject_Print는 재귀적 탐색을 해서 String의 경우에는 PyString_Print가 내부 함수인 string_print를 호출해서 최종적으로 출력하는 데, 이 때에 flags에 Py_PRINT_RAW가 세팅되는 경우에는 그냥 fwrite를 하기 때문에 한글이 제대로 나오는데, 0인 경우에는 한글이 모두 이스케이프되어 버림.

    • PyFile_WriteObject에서 출력하는 오브젝트가 StringIO같은 가짜 파일인 경우에는 PyObject_Repr로 일단 String으로 만든 뒤에 그 녀석을 인자로 해서 출력 오브젝트의 write를 호출.

    여기서, 첫번째 방법으로 출력이 되는 경우에는 그다지 어렵지가 않은 것이, Py_PRINT_RICH라는 플래그를 하나 만들어 줘서, 그 녀석을 계속 끝의 flags에 달아 다니다가, PyString_Print와 PyUnicode_Print에서만 적당히 처리해서 뿌려주면 될 것 같습니다.

    그런데, 이제 두번째 경우에는 repr()이 호출되어서 PyObject_Repr에 들어온 것인지, 출력하려고 PyObject_Repr에 들어온 것인지 하위 오브젝트들에게 전달해 줄 수 있는 방법이 전혀 없기 때문에, 결국은 새로 오브젝트 프로토콜 메쏘드를 하나 만들어 주는 수 밖에 없는 걸로 보입니다. 그래서, 결국 typedef PyObject *(*richreprfunc)(PyObject *v, const char *encoding); 타입으로 하나 만들어서 typeobject의 제일 끝에 추가하고 PyObject_RichRepr을 PyFile_WriteObject에서 사용하도록 하려고 생각하고 있습니다.

    아직은 가능성을 위해서 이것 저것 테스트해 보고 있구요 ~.~; 우선, CJKPython 2.3final에서 표준 2.3과 호환성을 유지하기 위해서는 타입 오브젝트의 크기가 변할 수 없기 때문에, SJIS 패치에서만 그냥 원래 일본식 repr을 쓰고 표준 타입에서는 sys.displayhook만 교체하는 방법을 쓰려고 합니다.

    파이썬 2.4에서는 다국어 문제가 말끔히 해결되었으면 좋겠네요. :) 혹시 좋은 아이디어 있으신 분들은 꼭 알려주세요~

    파이썬 포트 2.3 업그레이드

    [FreshPorts]lang/python 을 파이썬 2.3으로 업그레이드하고, [FreshPorts]lang/python22 에 파이썬 2.2를 레포카피한 뒤에 altinstall로 바꿔두었습니다. 대충 파이썬 포트들 한 20개 정도 테스트해 보니 호환성에는 별 문제가 없는 듯 하네요. ~.~

    자자 모두들 portupgrade python 때리시고…. 흐흐흐 =3

    [FreshPorts]lang/python-devel 포트는 2.4a0 오늘자 스냅샷을 뽑아서 올릴 생각입니다. 참 그리고, 오늘 [FreshPorts]lang/jython 이 드디어 [WWW]2.2a0이 발표되었더군용. Jython 팬들은 꼭 확인을 ~.~