1# common python utility routines for the Bionic tool scripts
2
3import sys, os, commands, string, commands
4
5# basic debugging trace support
6# call D_setlevel to set the verbosity level
7# and D(), D2(), D3(), D4() to add traces
8#
9verbose = 0
10
11def panic(msg):
12    sys.stderr.write( find_program_name() + ": error: " )
13    sys.stderr.write( msg )
14    sys.exit(1)
15
16def D(msg):
17    global verbose
18    if verbose > 0:
19        print msg
20
21def D2(msg):
22    global verbose
23    if verbose >= 2:
24        print msg
25
26def D3(msg):
27    global verbose
28    if verbose >= 3:
29        print msg
30
31def D4(msg):
32    global verbose
33    if verbose >= 4:
34        print msg
35
36def D_setlevel(level):
37    global verbose
38    verbose = level
39
40
41#  other stuff
42#
43#
44def find_program_name():
45    return os.path.basename(sys.argv[0])
46
47def find_program_dir():
48    return os.path.dirname(sys.argv[0])
49
50def find_file_from_upwards(from_path,target_file):
51    """find a file in the current directory or its parents. if 'from_path' is None,
52       seach from the current program's directory"""
53    path = from_path
54    if path == None:
55        path = os.path.realpath(sys.argv[0])
56        path = os.path.dirname(path)
57        D("this script seems to be located in: %s" % path)
58
59    while 1:
60        D("probing "+path)
61        if path == "":
62            file = target_file
63        else:
64            file = path + "/" + target_file
65
66        if os.path.isfile(file):
67            D("found %s in %s" % (target_file, path))
68            return file
69
70        if path == "":
71            return None
72
73        path = os.path.dirname(path)
74
75def find_bionic_root():
76    file = find_file_from_upwards(None, "SYSCALLS.TXT")
77    if file:
78        return os.path.dirname(file)
79    else:
80        return None
81
82def find_kernel_headers():
83    """try to find the directory containing the kernel headers for this machine"""
84    status, version = commands.getstatusoutput( "uname -r" )  # get Linux kernel version
85    if status != 0:
86        D("could not execute 'uname -r' command properly")
87        return None
88
89    # get rid of the "-xenU" suffix that is found in Xen virtual machines
90    if len(version) > 5 and version[-5:] == "-xenU":
91        version = version[:-5]
92
93    path = "/usr/src/linux-headers-" + version
94    D("probing %s for kernel headers" % (path+"/include"))
95    ret = os.path.isdir( path )
96    if ret:
97        D("found kernel headers in: %s" % (path + "/include"))
98        return path
99    return None
100
101
102# parser for the SYSCALLS.TXT file
103#
104class SysCallsTxtParser:
105    def __init__(self):
106        self.syscalls = []
107        self.lineno   = 0
108
109    def E(msg):
110        print "%d: %s" % (self.lineno, msg)
111
112    def parse_line(self, line):
113        pos_lparen = line.find('(')
114        E          = self.E
115        if pos_lparen < 0:
116            E("missing left parenthesis in '%s'" % line)
117            return
118
119        pos_rparen = line.rfind(')')
120        if pos_rparen < 0 or pos_rparen <= pos_lparen:
121            E("missing or misplaced right parenthesis in '%s'" % line)
122            return
123
124        return_type = line[:pos_lparen].strip().split()
125        if len(return_type) < 2:
126            E("missing return type in '%s'" % line)
127            return
128
129        syscall_func = return_type[-1]
130        return_type  = string.join(return_type[:-1],' ')
131
132        pos_colon = syscall_func.find(':')
133        if pos_colon < 0:
134            syscall_name = syscall_func
135        else:
136            if pos_colon == 0 or pos_colon+1 >= len(syscall_func):
137                E("misplaced colon in '%s'" % line)
138                return
139            syscall_name = syscall_func[pos_colon+1:]
140            syscall_func = syscall_func[:pos_colon]
141
142        if pos_rparen > pos_lparen+1:
143            syscall_params = line[pos_lparen+1:pos_rparen].split(',')
144            params         = string.join(syscall_params,',')
145        else:
146            syscall_params = []
147            params         = "void"
148
149        number = line[pos_rparen+1:].strip()
150        if number == "stub":
151            syscall_id  = -1
152            syscall_id2 = -1
153        else:
154            try:
155                if number[0] == '#':
156                    number = number[1:].strip()
157                numbers = string.split(number,',')
158                syscall_id  = int(numbers[0])
159                syscall_id2 = syscall_id
160                if len(numbers) > 1:
161                    syscall_id2 = int(numbers[1])
162            except:
163                E("invalid syscall number in '%s'" % line)
164                return
165
166        t = { "id"     : syscall_id,
167              "id2"    : syscall_id2,
168              "name"   : syscall_name,
169              "func"   : syscall_func,
170              "params" : syscall_params,
171              "decl"   : "%-15s  %s (%s);" % (return_type, syscall_func, params) }
172
173        self.syscalls.append(t)
174
175    def parse_file(self, file_path):
176        fp = open(file_path)
177        for line in fp.xreadlines():
178            self.lineno += 1
179            line = line.strip()
180            if not line: continue
181            if line[0] == '#': continue
182            self.parse_line(line)
183
184        fp.close()
185
186
187class Output:
188    def  __init__(self,out=sys.stdout):
189        self.out = out
190
191    def write(self,msg):
192        self.out.write(msg)
193
194    def writeln(self,msg):
195        self.out.write(msg)
196        self.out.write("\n")
197
198class StringOutput:
199    def __init__(self):
200        self.line = ""
201
202    def write(self,msg):
203        self.line += msg
204        D2("write '%s'" % msg)
205
206    def writeln(self,msg):
207        self.line += msg + '\n'
208        D2("write '%s\\n'"% msg)
209
210    def get(self):
211        return self.line
212
213
214def create_file_path(path):
215    dirs = []
216    while 1:
217        parent = os.path.dirname(path)
218        #print "parent: %s <- %s" % (parent, path)
219        if parent == "/" or parent == "":
220            break
221        dirs.append(parent)
222        path = parent
223
224    dirs.reverse()
225    for dir in dirs:
226        #print "dir %s" % dir
227        if os.path.isdir(dir):
228            continue
229        os.mkdir(dir)
230
231def walk_source_files(paths,callback,args,excludes=[]):
232    """recursively walk a list of paths and files, only keeping the source files in directories"""
233    for path in paths:
234        if len(path) > 0 and path[0] == '@':
235            # this is the name of another file, include it and parse it
236            path = path[1:]
237            if os.path.exists(path):
238                for line in open(path):
239                    if len(line) > 0 and line[-1] == '\n':
240                        line = line[:-1]
241                    walk_source_files([line],callback,args,excludes)
242            continue
243        if not os.path.isdir(path):
244            callback(path,args)
245        else:
246            for root, dirs, files in os.walk(path):
247                #print "w-- %s (ex: %s)" % (repr((root,dirs)), repr(excludes))
248                if len(excludes):
249                    for d in dirs[:]:
250                        if os.path.join(root,d) in excludes:
251                            dirs.remove(d)
252                for f in files:
253                    r, ext = os.path.splitext(f)
254                    if ext in [ ".h", ".c", ".cpp", ".S" ]:
255                        callback( "%s/%s" % (root,f), args )
256
257def cleanup_dir(path):
258    """create a directory if needed, and ensure that it is totally empty
259       by removing any existing content in it"""
260    if not os.path.exists(path):
261        os.mkdir(path)
262    else:
263        for root, dirs, files in os.walk(path, topdown=False):
264            if root.endswith("kernel_headers/"):
265                # skip 'kernel_headers'
266                continue
267            for name in files:
268                os.remove(os.path.join(root, name))
269            for name in dirs:
270                os.rmdir(os.path.join(root, name))
271
272def update_file( path, newdata ):
273    """update a file on disk, only if its content has changed"""
274    if os.path.exists( path ):
275        try:
276            f = open( path, "r" )
277            olddata = f.read()
278            f.close()
279        except:
280            D("update_file: cannot read existing file '%s'" % path)
281            return 0
282
283        if oldata == newdata:
284            D2("update_file: no change to file '%s'" % path )
285            return 0
286
287        update = 1
288    else:
289        try:
290            create_file_path(path)
291        except:
292            D("update_file: cannot create path to '%s'" % path)
293            return 0
294
295    f = open( path, "w" )
296    f.write( newdata )
297    f.close()
298
299    return 1
300
301
302class BatchFileUpdater:
303    """a class used to edit several files at once"""
304    def __init__(self):
305        self.old_files = set()
306        self.new_files = set()
307        self.new_data  = {}
308
309    def readFile(self,path):
310        #path = os.path.realpath(path)
311        if os.path.exists(path):
312            self.old_files.add(path)
313
314    def readDir(self,path):
315        #path = os.path.realpath(path)
316        for root, dirs, files in os.walk(path):
317            for f in files:
318                dst = "%s/%s" % (root,f)
319                self.old_files.add(dst)
320
321    def editFile(self,dst,data):
322        """edit a destination file. if the file is not mapped from a source,
323           it will be added. return 0 if the file content wasn't changed,
324           1 if it was edited, or 2 if the file is new"""
325        #dst = os.path.realpath(dst)
326        result = 1
327        if os.path.exists(dst):
328            f = open(dst, "r")
329            olddata = f.read()
330            f.close()
331            if olddata == data:
332                self.old_files.remove(dst)
333                return 0
334        else:
335            result = 2
336
337        self.new_data[dst] = data
338        self.new_files.add(dst)
339        return result
340
341    def getChanges(self):
342        """determine changes, returns (adds, deletes, edits)"""
343        adds    = set()
344        edits   = set()
345        deletes = set()
346
347        for dst in self.new_files:
348            if not (dst in self.old_files):
349                adds.add(dst)
350            else:
351                edits.add(dst)
352
353        for dst in self.old_files:
354            if not dst in self.new_files:
355                deletes.add(dst)
356
357        return (adds, deletes, edits)
358
359    def _writeFile(self,dst,data=None):
360        if not os.path.exists(os.path.dirname(dst)):
361            create_file_path(dst)
362        if data == None:
363            data = self.new_data[dst]
364        f = open(dst, "w")
365        f.write(self.new_data[dst])
366        f.close()
367
368    def updateFiles(self):
369        adds, deletes, edits = self.getChanges()
370
371        for dst in sorted(adds):
372            self._writeFile(dst)
373
374        for dst in sorted(edits):
375            self._writeFile(dst)
376
377        for dst in sorted(deletes):
378            os.remove(dst)
379
380    def updateP4Files(self):
381        adds, deletes, edits = self.getChanges()
382
383        if len(adds):
384            files = string.join(sorted(adds)," ")
385            D( "%d new files will be p4 add-ed" % len(adds) )
386            for dst in adds:
387                self._writeFile(dst)
388            D2("P4 ADDS: %s" % files)
389            o = commands.getoutput( "p4 add " + files )
390            D2( o )
391
392        if len(edits):
393            files = string.join(sorted(edits)," ")
394            D( "%d files will be p4 edit-ed" % len(edits) )
395            D2("P4 EDITS: %s" % files)
396            o = commands.getoutput( "p4 edit " + files )
397            D2( o )
398            for dst in edits:
399                self._writeFile(dst)
400
401        if len(deletes):
402            files = string.join(sorted(deletes)," ")
403            D( "%d files will be p4 delete-d" % len(deletes) )
404            D2("P4 DELETES: %s" % files)
405            o = commands.getoutput( "p4 delete " + files )
406            D2( o )
407
408    def updateGitFiles(self):
409        adds, deletes, edits = self.getChanges()
410
411        if adds:
412            for dst in sorted(adds):
413                self._writeFile(dst)
414            commands.getoutput("git add " + " ".join(adds))
415
416        if deletes:
417            commands.getoutput("git rm " + " ".join(deletes))
418
419        if edits:
420            for dst in sorted(edits):
421                self._writeFile(dst)
422            commands.getoutput("git add " + " ".join(edits))
423