##// END OF EJS Templates
osutil: fix up check-code issues
Matt Mackall -
r13736:f3c4421e default
parent child Browse files
Show More
@@ -1,308 +1,308
1 1 #!/usr/bin/env python
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import re, glob, os, sys
11 11 import keyword
12 12 import optparse
13 13
14 14 def repquote(m):
15 15 t = re.sub(r"\w", "x", m.group('text'))
16 16 t = re.sub(r"[^\sx]", "o", t)
17 17 return m.group('quote') + t + m.group('quote')
18 18
19 19 def reppython(m):
20 20 comment = m.group('comment')
21 21 if comment:
22 22 return "#" * len(comment)
23 23 return repquote(m)
24 24
25 25 def repcomment(m):
26 26 return m.group(1) + "#" * len(m.group(2))
27 27
28 28 def repccomment(m):
29 29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 30 return m.group(1) + t + "*/"
31 31
32 32 def repcallspaces(m):
33 33 t = re.sub(r"\n\s+", "\n", m.group(2))
34 34 return m.group(1) + t
35 35
36 36 def repinclude(m):
37 37 return m.group(1) + "<foo>"
38 38
39 39 def rephere(m):
40 40 t = re.sub(r"\S", "x", m.group(2))
41 41 return m.group(1) + t
42 42
43 43
44 44 testpats = [
45 45 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
46 46 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
47 47 (r'^function', "don't use 'function', use old style"),
48 48 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
49 49 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
50 50 (r'echo -n', "don't use 'echo -n', use printf"),
51 51 (r'^diff.*-\w*N', "don't use 'diff -N'"),
52 52 (r'(^| )wc[^|]*$', "filter wc output"),
53 53 (r'head -c', "don't use 'head -c', use 'dd'"),
54 54 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
55 55 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
56 56 (r'printf.*\\x', "don't use printf \\x, use Python"),
57 57 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
58 58 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
59 59 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
60 60 "use egrep for extended grep syntax"),
61 61 (r'/bin/', "don't use explicit paths for tools"),
62 62 (r'\$PWD', "don't use $PWD, use `pwd`"),
63 63 (r'[^\n]\Z', "no trailing newline"),
64 64 (r'export.*=', "don't export and assign at once"),
65 65 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
66 66 (r'^source\b', "don't use 'source', use '.'"),
67 67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
68 68 (r'ls\s+[^|-]+\s+-', "options to 'ls' must come before filenames"),
69 69 (r'[^>]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
70 70 ]
71 71
72 72 testfilters = [
73 73 (r"( *)(#([^\n]*\S)?)", repcomment),
74 74 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
75 75 ]
76 76
77 77 uprefix = r"^ \$ "
78 78 uprefixc = r"^ > "
79 79 utestpats = [
80 80 (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"),
81 81 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
82 82 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
83 83 (uprefix + r'.*\$\?', "explicit exit code checks unnecessary"),
84 84 (uprefix + r'.*\|\| echo.*(fail|error)',
85 85 "explicit exit code checks unnecessary"),
86 86 (uprefix + r'set -e', "don't use set -e"),
87 87 (uprefixc + r'( *)\t', "don't use tabs to indent"),
88 88 ]
89 89
90 90 for p, m in testpats:
91 91 if p.startswith('^'):
92 92 p = uprefix + p[1:]
93 93 else:
94 94 p = uprefix + p
95 95 utestpats.append((p, m))
96 96
97 97 utestfilters = [
98 98 (r"( *)(#([^\n]*\S)?)", repcomment),
99 99 ]
100 100
101 101 pypats = [
102 102 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
103 103 "tuple parameter unpacking not available in Python 3+"),
104 104 (r'lambda\s*\(.*,.*\)',
105 105 "tuple parameter unpacking not available in Python 3+"),
106 106 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
107 107 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
108 108 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
109 109 (r'^\s*\t', "don't use tabs"),
110 110 (r'\S;\s*\n', "semicolon"),
111 111 (r'\w,\w', "missing whitespace after ,"),
112 112 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
113 113 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
114 114 (r'.{85}', "line too long"),
115 115 (r'.{81}', "warning: line over 80 characters"),
116 116 (r'[^\n]\Z', "no trailing newline"),
117 117 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
118 118 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
119 119 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
120 120 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
121 121 "linebreak after :"),
122 122 (r'class\s[^(]:', "old-style class, use class foo(object)"),
123 123 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
124 124 "Python keyword is not a function"),
125 125 (r',]', "unneeded trailing ',' in list"),
126 126 # (r'class\s[A-Z][^\(]*\((?!Exception)',
127 127 # "don't capitalize non-exception classes"),
128 128 # (r'in range\(', "use xrange"),
129 129 # (r'^\s*print\s+', "avoid using print in core and extensions"),
130 130 (r'[\x80-\xff]', "non-ASCII character literal"),
131 131 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
132 132 (r'^\s*with\s+', "with not available in Python 2.4"),
133 133 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
134 134 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
135 135 (r'(?<!def)\s+(any|all|format)\(',
136 136 "any/all/format not available in Python 2.4"),
137 137 (r'(?<!def)\s+(callable)\(',
138 138 "callable not available in Python 3, use hasattr(f, '__call__')"),
139 139 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
140 140 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
141 141 "gratuitous whitespace after Python keyword"),
142 142 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
143 143 # (r'\s\s=', "gratuitous whitespace before ="),
144 144 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
145 145 "missing whitespace around operator"),
146 146 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
147 147 "missing whitespace around operator"),
148 148 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
149 149 "missing whitespace around operator"),
150 150 (r'[^+=*!<>&| -](\s=|=\s)[^= ]',
151 151 "wrong whitespace around ="),
152 152 (r'raise Exception', "don't raise generic exceptions"),
153 153 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
154 154 "warning: unwrapped ui message"),
155 155 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
156 156 (r' [=!]=\s+(True|False|None)',
157 157 "comparison with singleton, use 'is' or 'is not' instead"),
158 158 ]
159 159
160 160 pyfilters = [
161 161 (r"""(?msx)(?P<comment>\#.*?$)|
162 162 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
163 163 (?P<text>(([^\\]|\\.)*?))
164 164 (?P=quote))""", reppython),
165 165 ]
166 166
167 167 cpats = [
168 168 (r'//', "don't use //-style comments"),
169 169 (r'^ ', "don't use spaces to indent"),
170 170 (r'\S\t', "don't use tabs except for indent"),
171 171 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
172 172 (r'.{85}', "line too long"),
173 173 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
174 174 (r'return\(', "return is not a function"),
175 175 (r' ;', "no space before ;"),
176 176 (r'\w+\* \w+', "use int *foo, not int* foo"),
177 177 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
178 178 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
179 179 (r'\w,\w', "missing whitespace after ,"),
180 (r'\w[+/*]\w', "missing whitespace in expression"),
180 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
181 181 (r'^#\s+\w', "use #foo, not # foo"),
182 182 (r'[^\n]\Z', "no trailing newline"),
183 183 ]
184 184
185 185 cfilters = [
186 186 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
187 187 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
188 188 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
189 189 (r'(\()([^)]+\))', repcallspaces),
190 190 ]
191 191
192 192 checks = [
193 193 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
194 194 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
195 195 ('c', r'.*\.c$', cfilters, cpats),
196 196 ('unified test', r'.*\.t$', utestfilters, utestpats),
197 197 ]
198 198
199 199 class norepeatlogger(object):
200 200 def __init__(self):
201 201 self._lastseen = None
202 202
203 203 def log(self, fname, lineno, line, msg, blame):
204 204 """print error related a to given line of a given file.
205 205
206 206 The faulty line will also be printed but only once in the case
207 207 of multiple errors.
208 208
209 209 :fname: filename
210 210 :lineno: line number
211 211 :line: actual content of the line
212 212 :msg: error message
213 213 """
214 214 msgid = fname, lineno, line
215 215 if msgid != self._lastseen:
216 216 if blame:
217 217 print "%s:%d (%s):" % (fname, lineno, blame)
218 218 else:
219 219 print "%s:%d:" % (fname, lineno)
220 220 print " > %s" % line
221 221 self._lastseen = msgid
222 222 print " " + msg
223 223
224 224 _defaultlogger = norepeatlogger()
225 225
226 226 def getblame(f):
227 227 lines = []
228 228 for l in os.popen('hg annotate -un %s' % f):
229 229 start, line = l.split(':', 1)
230 230 user, rev = start.split()
231 231 lines.append((line[1:-1], user, rev))
232 232 return lines
233 233
234 234 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
235 235 blame=False):
236 236 """checks style and portability of a given file
237 237
238 238 :f: filepath
239 239 :logfunc: function used to report error
240 240 logfunc(filename, linenumber, linecontent, errormessage)
241 241 :maxerr: number of error to display before arborting.
242 242 Set to None (default) to report all errors
243 243
244 244 return True if no error is found, False otherwise.
245 245 """
246 246 blamecache = None
247 247 result = True
248 248 for name, match, filters, pats in checks:
249 249 fc = 0
250 250 if not re.match(match, f):
251 251 continue
252 252 fp = open(f)
253 253 pre = post = fp.read()
254 254 fp.close()
255 255 if "no-" + "check-code" in pre:
256 256 break
257 257 for p, r in filters:
258 258 post = re.sub(p, r, post)
259 259 # print post # uncomment to show filtered version
260 260 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
261 261 for n, l in z:
262 262 if "check-code" + "-ignore" in l[0]:
263 263 continue
264 264 for p, msg in pats:
265 265 if not warnings and msg.startswith("warning"):
266 266 continue
267 267 if re.search(p, l[1]):
268 268 bd = ""
269 269 if blame:
270 270 bd = 'working directory'
271 271 if not blamecache:
272 272 blamecache = getblame(f)
273 273 if n < len(blamecache):
274 274 bl, bu, br = blamecache[n]
275 275 if bl == l[0]:
276 276 bd = '%s@%s' % (bu, br)
277 277 logfunc(f, n + 1, l[0], msg, bd)
278 278 fc += 1
279 279 result = False
280 280 if maxerr is not None and fc >= maxerr:
281 281 print " (too many errors, giving up)"
282 282 break
283 283 break
284 284 return result
285 285
286 286 if __name__ == "__main__":
287 287 parser = optparse.OptionParser("%prog [options] [files]")
288 288 parser.add_option("-w", "--warnings", action="store_true",
289 289 help="include warning-level checks")
290 290 parser.add_option("-p", "--per-file", type="int",
291 291 help="max warnings per file")
292 292 parser.add_option("-b", "--blame", action="store_true",
293 293 help="use annotate to generate blame info")
294 294
295 295 parser.set_defaults(per_file=15, warnings=False, blame=False)
296 296 (options, args) = parser.parse_args()
297 297
298 298 if len(args) == 0:
299 299 check = glob.glob("*")
300 300 else:
301 301 check = args
302 302
303 303 for f in check:
304 304 ret = 0
305 305 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
306 306 blame=options.blame):
307 307 ret = 1
308 308 sys.exit(ret)
@@ -1,576 +1,576
1 1 /*
2 2 osutil.c - native operating system services
3 3
4 4 Copyright 2007 Matt Mackall and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define _ATFILE_SOURCE
11 11 #include <Python.h>
12 12 #include <fcntl.h>
13 13 #include <stdio.h>
14 14 #include <string.h>
15 15
16 16 #ifdef _WIN32
17 17 #include <windows.h>
18 18 #include <io.h>
19 19 #else
20 20 #include <dirent.h>
21 21 #include <sys/stat.h>
22 22 #include <sys/types.h>
23 23 #include <unistd.h>
24 24 #endif
25 25
26 26 #include "util.h"
27 27
28 28 /* some platforms lack the PATH_MAX definition (eg. GNU/Hurd) */
29 29 #ifndef PATH_MAX
30 30 #define PATH_MAX 4096
31 31 #endif
32 32
33 33 #ifdef _WIN32
34 34 /*
35 35 stat struct compatible with hg expectations
36 36 Mercurial only uses st_mode, st_size and st_mtime
37 37 the rest is kept to minimize changes between implementations
38 38 */
39 39 struct hg_stat {
40 40 int st_dev;
41 41 int st_mode;
42 42 int st_nlink;
43 43 __int64 st_size;
44 44 int st_mtime;
45 45 int st_ctime;
46 46 };
47 47 struct listdir_stat {
48 48 PyObject_HEAD
49 49 struct hg_stat st;
50 50 };
51 51 #else
52 52 struct listdir_stat {
53 53 PyObject_HEAD
54 54 struct stat st;
55 55 };
56 56 #endif
57 57
58 58 #define listdir_slot(name) \
59 59 static PyObject *listdir_stat_##name(PyObject *self, void *x) \
60 60 { \
61 61 return PyInt_FromLong(((struct listdir_stat *)self)->st.name); \
62 62 }
63 63
64 64 listdir_slot(st_dev)
65 65 listdir_slot(st_mode)
66 66 listdir_slot(st_nlink)
67 67 #ifdef _WIN32
68 68 static PyObject *listdir_stat_st_size(PyObject *self, void *x)
69 69 {
70 70 return PyLong_FromLongLong(
71 71 (PY_LONG_LONG)((struct listdir_stat *)self)->st.st_size);
72 72 }
73 73 #else
74 74 listdir_slot(st_size)
75 75 #endif
76 76 listdir_slot(st_mtime)
77 77 listdir_slot(st_ctime)
78 78
79 79 static struct PyGetSetDef listdir_stat_getsets[] = {
80 80 {"st_dev", listdir_stat_st_dev, 0, 0, 0},
81 81 {"st_mode", listdir_stat_st_mode, 0, 0, 0},
82 82 {"st_nlink", listdir_stat_st_nlink, 0, 0, 0},
83 83 {"st_size", listdir_stat_st_size, 0, 0, 0},
84 84 {"st_mtime", listdir_stat_st_mtime, 0, 0, 0},
85 85 {"st_ctime", listdir_stat_st_ctime, 0, 0, 0},
86 86 {0, 0, 0, 0, 0}
87 87 };
88 88
89 89 static PyObject *listdir_stat_new(PyTypeObject *t, PyObject *a, PyObject *k)
90 90 {
91 91 return t->tp_alloc(t, 0);
92 92 }
93 93
94 94 static void listdir_stat_dealloc(PyObject *o)
95 95 {
96 96 o->ob_type->tp_free(o);
97 97 }
98 98
99 99 static PyTypeObject listdir_stat_type = {
100 100 PyVarObject_HEAD_INIT(NULL, 0)
101 101 "osutil.stat", /*tp_name*/
102 102 sizeof(struct listdir_stat), /*tp_basicsize*/
103 103 0, /*tp_itemsize*/
104 104 (destructor)listdir_stat_dealloc, /*tp_dealloc*/
105 105 0, /*tp_print*/
106 106 0, /*tp_getattr*/
107 107 0, /*tp_setattr*/
108 108 0, /*tp_compare*/
109 109 0, /*tp_repr*/
110 110 0, /*tp_as_number*/
111 111 0, /*tp_as_sequence*/
112 112 0, /*tp_as_mapping*/
113 113 0, /*tp_hash */
114 114 0, /*tp_call*/
115 115 0, /*tp_str*/
116 116 0, /*tp_getattro*/
117 117 0, /*tp_setattro*/
118 118 0, /*tp_as_buffer*/
119 119 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
120 120 "stat objects", /* tp_doc */
121 121 0, /* tp_traverse */
122 122 0, /* tp_clear */
123 123 0, /* tp_richcompare */
124 124 0, /* tp_weaklistoffset */
125 125 0, /* tp_iter */
126 126 0, /* tp_iternext */
127 127 0, /* tp_methods */
128 128 0, /* tp_members */
129 129 listdir_stat_getsets, /* tp_getset */
130 130 0, /* tp_base */
131 131 0, /* tp_dict */
132 132 0, /* tp_descr_get */
133 133 0, /* tp_descr_set */
134 134 0, /* tp_dictoffset */
135 135 0, /* tp_init */
136 136 0, /* tp_alloc */
137 137 listdir_stat_new, /* tp_new */
138 138 };
139 139
140 140 #ifdef _WIN32
141 141
142 142 static int to_python_time(const FILETIME *tm)
143 143 {
144 144 /* number of seconds between epoch and January 1 1601 */
145 145 const __int64 a0 = (__int64)134774L * (__int64)24L * (__int64)3600L;
146 146 /* conversion factor from 100ns to 1s */
147 147 const __int64 a1 = 10000000;
148 148 /* explicit (int) cast to suspend compiler warnings */
149 149 return (int)((((__int64)tm->dwHighDateTime << 32)
150 150 + tm->dwLowDateTime) / a1 - a0);
151 151 }
152 152
153 153 static PyObject *make_item(const WIN32_FIND_DATAA *fd, int wantstat)
154 154 {
155 155 PyObject *py_st;
156 156 struct hg_stat *stp;
157 157
158 158 int kind = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
159 159 ? _S_IFDIR : _S_IFREG;
160 160
161 161 if (!wantstat)
162 162 return Py_BuildValue("si", fd->cFileName, kind);
163 163
164 164 py_st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
165 165 if (!py_st)
166 166 return NULL;
167 167
168 168 stp = &((struct listdir_stat *)py_st)->st;
169 169 /*
170 170 use kind as st_mode
171 171 rwx bits on Win32 are meaningless
172 172 and Hg does not use them anyway
173 173 */
174 174 stp->st_mode = kind;
175 175 stp->st_mtime = to_python_time(&fd->ftLastWriteTime);
176 176 stp->st_ctime = to_python_time(&fd->ftCreationTime);
177 177 if (kind == _S_IFREG)
178 178 stp->st_size = ((__int64)fd->nFileSizeHigh << 32)
179 179 + fd->nFileSizeLow;
180 180 return Py_BuildValue("siN", fd->cFileName,
181 181 kind, py_st);
182 182 }
183 183
184 184 static PyObject *_listdir(char *path, int plen, int wantstat, char *skip)
185 185 {
186 186 PyObject *rval = NULL; /* initialize - return value */
187 187 PyObject *list;
188 188 HANDLE fh;
189 189 WIN32_FIND_DATAA fd;
190 190 char *pattern;
191 191
192 192 /* build the path + \* pattern string */
193 193 pattern = malloc(plen + 3); /* path + \* + \0 */
194 194 if (!pattern) {
195 195 PyErr_NoMemory();
196 196 goto error_nomem;
197 197 }
198 198 strcpy(pattern, path);
199 199
200 200 if (plen > 0) {
201 201 char c = path[plen-1];
202 202 if (c != ':' && c != '/' && c != '\\')
203 203 pattern[plen++] = '\\';
204 204 }
205 205 strcpy(pattern + plen, "*");
206 206
207 207 fh = FindFirstFileA(pattern, &fd);
208 208 if (fh == INVALID_HANDLE_VALUE) {
209 209 PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
210 210 goto error_file;
211 211 }
212 212
213 213 list = PyList_New(0);
214 214 if (!list)
215 215 goto error_list;
216 216
217 217 do {
218 218 PyObject *item;
219 219
220 220 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
221 221 if (!strcmp(fd.cFileName, ".")
222 222 || !strcmp(fd.cFileName, ".."))
223 223 continue;
224 224
225 225 if (skip && !strcmp(fd.cFileName, skip)) {
226 226 rval = PyList_New(0);
227 227 goto error;
228 228 }
229 229 }
230 230
231 231 item = make_item(&fd, wantstat);
232 232 if (!item)
233 233 goto error;
234 234
235 235 if (PyList_Append(list, item)) {
236 236 Py_XDECREF(item);
237 237 goto error;
238 238 }
239 239
240 240 Py_XDECREF(item);
241 241 } while (FindNextFileA(fh, &fd));
242 242
243 243 if (GetLastError() != ERROR_NO_MORE_FILES) {
244 244 PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
245 245 goto error;
246 246 }
247 247
248 248 rval = list;
249 249 Py_XINCREF(rval);
250 250 error:
251 251 Py_XDECREF(list);
252 252 error_list:
253 253 FindClose(fh);
254 254 error_file:
255 255 free(pattern);
256 256 error_nomem:
257 257 return rval;
258 258 }
259 259
260 260 #else
261 261
262 262 int entkind(struct dirent *ent)
263 263 {
264 264 #ifdef DT_REG
265 265 switch (ent->d_type) {
266 266 case DT_REG: return S_IFREG;
267 267 case DT_DIR: return S_IFDIR;
268 268 case DT_LNK: return S_IFLNK;
269 269 case DT_BLK: return S_IFBLK;
270 270 case DT_CHR: return S_IFCHR;
271 271 case DT_FIFO: return S_IFIFO;
272 272 case DT_SOCK: return S_IFSOCK;
273 273 }
274 274 #endif
275 275 return -1;
276 276 }
277 277
278 278 static PyObject *_listdir(char *path, int pathlen, int keepstat, char *skip)
279 279 {
280 280 PyObject *list, *elem, *stat, *ret = NULL;
281 281 char fullpath[PATH_MAX + 10];
282 282 int kind, err;
283 283 struct stat st;
284 284 struct dirent *ent;
285 285 DIR *dir;
286 286 #ifdef AT_SYMLINK_NOFOLLOW
287 287 int dfd = -1;
288 288 #endif
289 289
290 290 if (pathlen >= PATH_MAX) {
291 291 PyErr_SetString(PyExc_ValueError, "path too long");
292 292 goto error_value;
293 293 }
294 294 strncpy(fullpath, path, PATH_MAX);
295 295 fullpath[pathlen] = '/';
296 296
297 297 #ifdef AT_SYMLINK_NOFOLLOW
298 298 dfd = open(path, O_RDONLY);
299 299 if (dfd == -1) {
300 300 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
301 301 goto error_value;
302 302 }
303 303 dir = fdopendir(dfd);
304 304 #else
305 305 dir = opendir(path);
306 306 #endif
307 307 if (!dir) {
308 308 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
309 309 goto error_dir;
310 310 }
311 311
312 312 list = PyList_New(0);
313 313 if (!list)
314 314 goto error_list;
315 315
316 316 while ((ent = readdir(dir))) {
317 317 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
318 318 continue;
319 319
320 320 kind = entkind(ent);
321 321 if (kind == -1 || keepstat) {
322 322 #ifdef AT_SYMLINK_NOFOLLOW
323 323 err = fstatat(dfd, ent->d_name, &st,
324 324 AT_SYMLINK_NOFOLLOW);
325 325 #else
326 326 strncpy(fullpath + pathlen + 1, ent->d_name,
327 327 PATH_MAX - pathlen);
328 328 fullpath[PATH_MAX] = 0;
329 329 err = lstat(fullpath, &st);
330 330 #endif
331 331 if (err == -1) {
332 332 strncpy(fullpath + pathlen + 1, ent->d_name,
333 333 PATH_MAX - pathlen);
334 334 fullpath[PATH_MAX] = 0;
335 335 PyErr_SetFromErrnoWithFilename(PyExc_OSError,
336 336 fullpath);
337 337 goto error;
338 338 }
339 339 kind = st.st_mode & S_IFMT;
340 340 }
341 341
342 342 /* quit early? */
343 343 if (skip && kind == S_IFDIR && !strcmp(ent->d_name, skip)) {
344 344 ret = PyList_New(0);
345 345 goto error;
346 346 }
347 347
348 348 if (keepstat) {
349 349 stat = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
350 350 if (!stat)
351 351 goto error;
352 352 memcpy(&((struct listdir_stat *)stat)->st, &st, sizeof(st));
353 353 elem = Py_BuildValue("siN", ent->d_name, kind, stat);
354 354 } else
355 355 elem = Py_BuildValue("si", ent->d_name, kind);
356 356 if (!elem)
357 357 goto error;
358 358
359 359 PyList_Append(list, elem);
360 360 Py_DECREF(elem);
361 361 }
362 362
363 363 ret = list;
364 364 Py_INCREF(ret);
365 365
366 366 error:
367 367 Py_DECREF(list);
368 368 error_list:
369 369 closedir(dir);
370 370 error_dir:
371 371 #ifdef AT_SYMLINK_NOFOLLOW
372 372 close(dfd);
373 373 #endif
374 374 error_value:
375 375 return ret;
376 376 }
377 377
378 378 #endif /* ndef _WIN32 */
379 379
380 380 static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs)
381 381 {
382 382 PyObject *statobj = NULL; /* initialize - optional arg */
383 383 PyObject *skipobj = NULL; /* initialize - optional arg */
384 384 char *path, *skip = NULL;
385 385 int wantstat, plen;
386 386
387 387 static char *kwlist[] = {"path", "stat", "skip", NULL};
388 388
389 389 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|OO:listdir",
390 390 kwlist, &path, &plen, &statobj, &skipobj))
391 391 return NULL;
392 392
393 393 wantstat = statobj && PyObject_IsTrue(statobj);
394 394
395 395 if (skipobj && skipobj != Py_None) {
396 396 skip = PyBytes_AsString(skipobj);
397 397 if (!skip)
398 398 return NULL;
399 399 }
400 400
401 401 return _listdir(path, plen, wantstat, skip);
402 402 }
403 403
404 404 #ifdef _WIN32
405 405 static PyObject *posixfile(PyObject *self, PyObject *args, PyObject *kwds)
406 406 {
407 407 static char *kwlist[] = {"name", "mode", "buffering", NULL};
408 408 PyObject *file_obj = NULL;
409 409 char *name = NULL;
410 410 char *mode = "rb";
411 411 DWORD access = 0;
412 412 DWORD creation;
413 413 HANDLE handle;
414 414 int fd, flags = 0;
415 415 int bufsize = -1;
416 416 char m0, m1, m2;
417 417 char fpmode[4];
418 418 int fppos = 0;
419 419 int plus;
420 420 FILE *fp;
421 421
422 422 if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:posixfile", kwlist,
423 423 Py_FileSystemDefaultEncoding,
424 424 &name, &mode, &bufsize))
425 425 return NULL;
426 426
427 427 m0 = mode[0];
428 428 m1 = m0 ? mode[1] : '\0';
429 429 m2 = m1 ? mode[2] : '\0';
430 430 plus = m1 == '+' || m2 == '+';
431 431
432 432 fpmode[fppos++] = m0;
433 433 if (m1 == 'b' || m2 == 'b') {
434 434 flags = _O_BINARY;
435 435 fpmode[fppos++] = 'b';
436 436 }
437 437 else
438 438 flags = _O_TEXT;
439 439 if (m0 == 'r' && !plus) {
440 440 flags |= _O_RDONLY;
441 441 access = GENERIC_READ;
442 442 } else {
443 443 /*
444 444 work around http://support.microsoft.com/kb/899149 and
445 445 set _O_RDWR for 'w' and 'a', even if mode has no '+'
446 446 */
447 447 flags |= _O_RDWR;
448 448 access = GENERIC_READ | GENERIC_WRITE;
449 449 fpmode[fppos++] = '+';
450 450 }
451 451 fpmode[fppos++] = '\0';
452 452
453 453 switch (m0) {
454 454 case 'r':
455 455 creation = OPEN_EXISTING;
456 456 break;
457 457 case 'w':
458 458 creation = CREATE_ALWAYS;
459 459 break;
460 460 case 'a':
461 461 creation = OPEN_ALWAYS;
462 462 flags |= _O_APPEND;
463 463 break;
464 464 default:
465 465 PyErr_Format(PyExc_ValueError,
466 466 "mode string must begin with one of 'r', 'w', "
467 467 "or 'a', not '%c'", m0);
468 468 goto bail;
469 469 }
470 470
471 471 handle = CreateFile(name, access,
472 472 FILE_SHARE_READ | FILE_SHARE_WRITE |
473 473 FILE_SHARE_DELETE,
474 474 NULL,
475 475 creation,
476 476 FILE_ATTRIBUTE_NORMAL,
477 477 0);
478 478
479 479 if (handle == INVALID_HANDLE_VALUE) {
480 480 PyErr_SetFromWindowsErrWithFilename(GetLastError(), name);
481 481 goto bail;
482 482 }
483 483
484 484 fd = _open_osfhandle((intptr_t)handle, flags);
485 485
486 486 if (fd == -1) {
487 487 CloseHandle(handle);
488 488 PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
489 489 goto bail;
490 490 }
491 491 #ifndef IS_PY3K
492 492 fp = _fdopen(fd, fpmode);
493 493 if (fp == NULL) {
494 494 _close(fd);
495 495 PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
496 496 goto bail;
497 497 }
498 498
499 499 file_obj = PyFile_FromFile(fp, name, mode, fclose);
500 500 if (file_obj == NULL) {
501 501 fclose(fp);
502 502 goto bail;
503 503 }
504 504
505 505 PyFile_SetBufSize(file_obj, bufsize);
506 506 #else
507 507 file_obj = PyFile_FromFd(fd, name, mode, bufsize, NULL, NULL, NULL, 1);
508 508 if (file_obj == NULL)
509 509 goto bail;
510 510 #endif
511 511 bail:
512 512 PyMem_Free(name);
513 513 return file_obj;
514 514 }
515 515 #endif
516 516
517 517 #ifdef __APPLE__
518 518 #import <ApplicationServices/ApplicationServices.h>
519 519
520 520 static PyObject *isgui(PyObject *self)
521 521 {
522 CFDictionaryRef dict = CGSessionCopyCurrentDictionary();
522 CFDictionaryRef dict = CGSessionCopyCurrentDictionary();
523 523
524 if (dict != NULL) {
525 CFRelease(dict);
526 return Py_True;
527 } else {
528 return Py_False;
529 }
524 if (dict != NULL) {
525 CFRelease(dict);
526 return Py_True;
527 } else {
528 return Py_False;
529 }
530 530 }
531 531 #endif
532 532
533 533 static char osutil_doc[] = "Native operating system services.";
534 534
535 535 static PyMethodDef methods[] = {
536 536 {"listdir", (PyCFunction)listdir, METH_VARARGS | METH_KEYWORDS,
537 537 "list a directory\n"},
538 538 #ifdef _WIN32
539 539 {"posixfile", (PyCFunction)posixfile, METH_VARARGS | METH_KEYWORDS,
540 540 "Open a file with POSIX-like semantics.\n"
541 541 "On error, this function may raise either a WindowsError or an IOError."},
542 542 #endif
543 543 #ifdef __APPLE__
544 {
545 "isgui", (PyCFunction)isgui, METH_NOARGS,
546 "Is a CoreGraphics session available?"
547 },
544 {
545 "isgui", (PyCFunction)isgui, METH_NOARGS,
546 "Is a CoreGraphics session available?"
547 },
548 548 #endif
549 549 {NULL, NULL}
550 550 };
551 551
552 552 #ifdef IS_PY3K
553 553 static struct PyModuleDef osutil_module = {
554 554 PyModuleDef_HEAD_INIT,
555 555 "osutil",
556 556 osutil_doc,
557 557 -1,
558 558 methods
559 559 };
560 560
561 561 PyMODINIT_FUNC PyInit_osutil(void)
562 562 {
563 563 if (PyType_Ready(&listdir_stat_type) < 0)
564 564 return NULL;
565 565
566 566 return PyModule_Create(&osutil_module);
567 567 }
568 568 #else
569 569 PyMODINIT_FUNC initosutil(void)
570 570 {
571 571 if (PyType_Ready(&listdir_stat_type) == -1)
572 572 return;
573 573
574 574 Py_InitModule3("osutil", methods, osutil_doc);
575 575 }
576 576 #endif
General Comments 0
You need to be logged in to leave comments. Login now