osutil.c
534 lines
| 12.1 KiB
| text/x-c
|
CLexer
/ mercurial / osutil.c
|
r5396 | /* | ||
osutil.c - native operating system services | ||||
Copyright 2007 Matt Mackall and others | ||||
This software may be used and distributed according to the terms of | ||||
the GNU General Public License, incorporated herein by reference. | ||||
*/ | ||||
|
r5463 | #define _ATFILE_SOURCE | ||
|
r5397 | #include <Python.h> | ||
|
r8330 | #include <fcntl.h> | ||
#include <stdio.h> | ||||
#include <string.h> | ||||
|
r7056 | #ifdef _WIN32 | ||
|
r8330 | # include <windows.h> | ||
# include <io.h> | ||||
|
r7056 | #else | ||
|
r8330 | # include <dirent.h> | ||
# include <sys/stat.h> | ||||
# include <sys/types.h> | ||||
# include <unistd.h> | ||||
|
r7056 | #endif | ||
|
r5396 | |||
|
r8722 | // some platforms lack the PATH_MAX definition (eg. GNU/Hurd) | ||
#ifndef PATH_MAX | ||||
#define PATH_MAX 4096 | ||||
#endif | ||||
|
r7056 | #ifdef _WIN32 | ||
/* | ||||
stat struct compatible with hg expectations | ||||
Mercurial only uses st_mode, st_size and st_mtime | ||||
the rest is kept to minimize changes between implementations | ||||
*/ | ||||
struct hg_stat { | ||||
int st_dev; | ||||
int st_mode; | ||||
int st_nlink; | ||||
__int64 st_size; | ||||
int st_mtime; | ||||
int st_ctime; | ||||
}; | ||||
struct listdir_stat { | ||||
PyObject_HEAD | ||||
struct hg_stat st; | ||||
}; | ||||
#else | ||||
|
r5396 | struct listdir_stat { | ||
|
r5421 | PyObject_HEAD | ||
struct stat st; | ||||
|
r5396 | }; | ||
|
r7056 | #endif | ||
|
r5396 | |||
#define listdir_slot(name) \ | ||||
|
r7190 | static PyObject *listdir_stat_##name(PyObject *self, void *x) \ | ||
{ \ | ||||
return PyInt_FromLong(((struct listdir_stat *)self)->st.name); \ | ||||
} | ||||
|
r5396 | |||
|
r5431 | listdir_slot(st_dev) | ||
listdir_slot(st_mode) | ||||
listdir_slot(st_nlink) | ||||
|
r7056 | #ifdef _WIN32 | ||
static PyObject *listdir_stat_st_size(PyObject *self, void *x) | ||||
{ | ||||
return PyLong_FromLongLong( | ||||
(PY_LONG_LONG)((struct listdir_stat *)self)->st.st_size); | ||||
} | ||||
#else | ||||
|
r5431 | listdir_slot(st_size) | ||
|
r7056 | #endif | ||
|
r5431 | listdir_slot(st_mtime) | ||
listdir_slot(st_ctime) | ||||
|
r5396 | |||
static struct PyGetSetDef listdir_stat_getsets[] = { | ||||
|
r5421 | {"st_dev", listdir_stat_st_dev, 0, 0, 0}, | ||
{"st_mode", listdir_stat_st_mode, 0, 0, 0}, | ||||
{"st_nlink", listdir_stat_st_nlink, 0, 0, 0}, | ||||
{"st_size", listdir_stat_st_size, 0, 0, 0}, | ||||
{"st_mtime", listdir_stat_st_mtime, 0, 0, 0}, | ||||
{"st_ctime", listdir_stat_st_ctime, 0, 0, 0}, | ||||
{0, 0, 0, 0, 0} | ||||
|
r5396 | }; | ||
static PyObject *listdir_stat_new(PyTypeObject *t, PyObject *a, PyObject *k) | ||||
{ | ||||
|
r5421 | return t->tp_alloc(t, 0); | ||
|
r5396 | } | ||
static void listdir_stat_dealloc(PyObject *o) | ||||
{ | ||||
|
r5421 | o->ob_type->tp_free(o); | ||
|
r5396 | } | ||
static PyTypeObject listdir_stat_type = { | ||||
|
r5421 | PyObject_HEAD_INIT(NULL) | ||
0, /*ob_size*/ | ||||
"osutil.stat", /*tp_name*/ | ||||
sizeof(struct listdir_stat), /*tp_basicsize*/ | ||||
0, /*tp_itemsize*/ | ||||
(destructor)listdir_stat_dealloc, /*tp_dealloc*/ | ||||
0, /*tp_print*/ | ||||
0, /*tp_getattr*/ | ||||
0, /*tp_setattr*/ | ||||
0, /*tp_compare*/ | ||||
0, /*tp_repr*/ | ||||
0, /*tp_as_number*/ | ||||
0, /*tp_as_sequence*/ | ||||
0, /*tp_as_mapping*/ | ||||
0, /*tp_hash */ | ||||
0, /*tp_call*/ | ||||
0, /*tp_str*/ | ||||
0, /*tp_getattro*/ | ||||
0, /*tp_setattro*/ | ||||
0, /*tp_as_buffer*/ | ||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ | ||||
"stat objects", /* tp_doc */ | ||||
0, /* tp_traverse */ | ||||
0, /* tp_clear */ | ||||
0, /* tp_richcompare */ | ||||
0, /* tp_weaklistoffset */ | ||||
0, /* tp_iter */ | ||||
0, /* tp_iternext */ | ||||
0, /* tp_methods */ | ||||
0, /* tp_members */ | ||||
listdir_stat_getsets, /* tp_getset */ | ||||
0, /* tp_base */ | ||||
0, /* tp_dict */ | ||||
0, /* tp_descr_get */ | ||||
0, /* tp_descr_set */ | ||||
0, /* tp_dictoffset */ | ||||
0, /* tp_init */ | ||||
0, /* tp_alloc */ | ||||
listdir_stat_new, /* tp_new */ | ||||
|
r5396 | }; | ||
|
r7056 | #ifdef _WIN32 | ||
static int to_python_time(const FILETIME *tm) | ||||
{ | ||||
/* number of seconds between epoch and January 1 1601 */ | ||||
const __int64 a0 = (__int64)134774L * (__int64)24L * (__int64)3600L; | ||||
/* conversion factor from 100ns to 1s */ | ||||
const __int64 a1 = 10000000; | ||||
/* explicit (int) cast to suspend compiler warnings */ | ||||
return (int)((((__int64)tm->dwHighDateTime << 32) | ||||
+ tm->dwLowDateTime) / a1 - a0); | ||||
} | ||||
static PyObject *make_item(const WIN32_FIND_DATAA *fd, int wantstat) | ||||
{ | ||||
PyObject *py_st; | ||||
struct hg_stat *stp; | ||||
int kind = (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) | ||||
? _S_IFDIR : _S_IFREG; | ||||
if (!wantstat) | ||||
return Py_BuildValue("si", fd->cFileName, kind); | ||||
py_st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL); | ||||
if (!py_st) | ||||
return NULL; | ||||
stp = &((struct listdir_stat *)py_st)->st; | ||||
/* | ||||
use kind as st_mode | ||||
rwx bits on Win32 are meaningless | ||||
and Hg does not use them anyway | ||||
*/ | ||||
stp->st_mode = kind; | ||||
stp->st_mtime = to_python_time(&fd->ftLastWriteTime); | ||||
stp->st_ctime = to_python_time(&fd->ftCreationTime); | ||||
if (kind == _S_IFREG) | ||||
stp->st_size = ((__int64)fd->nFileSizeHigh << 32) | ||||
+ fd->nFileSizeLow; | ||||
return Py_BuildValue("siN", fd->cFileName, | ||||
kind, py_st); | ||||
} | ||||
|
r7098 | static PyObject *_listdir(char *path, int plen, int wantstat, char *skip) | ||
|
r7056 | { | ||
PyObject *rval = NULL; /* initialize - return value */ | ||||
PyObject *list; | ||||
HANDLE fh; | ||||
WIN32_FIND_DATAA fd; | ||||
|
r7098 | char *pattern; | ||
|
r7056 | |||
/* build the path + \* pattern string */ | ||||
pattern = malloc(plen+3); /* path + \* + \0 */ | ||||
if (!pattern) { | ||||
PyErr_NoMemory(); | ||||
|
r7098 | goto error_nomem; | ||
|
r7056 | } | ||
strcpy(pattern, path); | ||||
if (plen > 0) { | ||||
char c = path[plen-1]; | ||||
if (c != ':' && c != '/' && c != '\\') | ||||
pattern[plen++] = '\\'; | ||||
} | ||||
strcpy(pattern + plen, "*"); | ||||
fh = FindFirstFileA(pattern, &fd); | ||||
if (fh == INVALID_HANDLE_VALUE) { | ||||
|
r7059 | PyErr_SetFromWindowsErrWithFilename(GetLastError(), path); | ||
|
r7056 | goto error_file; | ||
} | ||||
list = PyList_New(0); | ||||
if (!list) | ||||
goto error_list; | ||||
do { | ||||
PyObject *item; | ||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | ||||
if (!strcmp(fd.cFileName, ".") | ||||
|| !strcmp(fd.cFileName, "..")) | ||||
continue; | ||||
if (skip && !strcmp(fd.cFileName, skip)) { | ||||
rval = PyList_New(0); | ||||
goto error; | ||||
} | ||||
} | ||||
item = make_item(&fd, wantstat); | ||||
if (!item) | ||||
goto error; | ||||
if (PyList_Append(list, item)) { | ||||
Py_XDECREF(item); | ||||
goto error; | ||||
} | ||||
Py_XDECREF(item); | ||||
} while (FindNextFileA(fh, &fd)); | ||||
if (GetLastError() != ERROR_NO_MORE_FILES) { | ||||
|
r7059 | PyErr_SetFromWindowsErrWithFilename(GetLastError(), path); | ||
|
r7056 | goto error; | ||
} | ||||
rval = list; | ||||
Py_XINCREF(rval); | ||||
error: | ||||
Py_XDECREF(list); | ||||
error_list: | ||||
FindClose(fh); | ||||
error_file: | ||||
free(pattern); | ||||
|
r7098 | error_nomem: | ||
|
r7056 | return rval; | ||
} | ||||
#else | ||||
|
r7031 | int entkind(struct dirent *ent) | ||
|
r5425 | { | ||
|
r7031 | #ifdef DT_REG | ||
switch (ent->d_type) { | ||||
case DT_REG: return S_IFREG; | ||||
case DT_DIR: return S_IFDIR; | ||||
case DT_LNK: return S_IFLNK; | ||||
case DT_BLK: return S_IFBLK; | ||||
case DT_CHR: return S_IFCHR; | ||||
case DT_FIFO: return S_IFIFO; | ||||
case DT_SOCK: return S_IFSOCK; | ||||
} | ||||
|
r5463 | #endif | ||
|
r7033 | return -1; | ||
|
r5425 | } | ||
|
r7098 | static PyObject *_listdir(char *path, int pathlen, int keepstat, char *skip) | ||
|
r5396 | { | ||
|
r7098 | PyObject *list, *elem, *stat, *ret = NULL; | ||
char fullpath[PATH_MAX + 10]; | ||||
|
r7136 | int kind, err; | ||
|
r7031 | struct stat st; | ||
struct dirent *ent; | ||||
DIR *dir; | ||||
|
r7136 | #ifdef AT_SYMLINK_NOFOLLOW | ||
int dfd = -1; | ||||
#endif | ||||
|
r5396 | |||
|
r7059 | if (pathlen >= PATH_MAX) { | ||
PyErr_SetString(PyExc_ValueError, "path too long"); | ||||
|
r7098 | goto error_value; | ||
|
r7059 | } | ||
|
r7031 | strncpy(fullpath, path, PATH_MAX); | ||
fullpath[pathlen] = '/'; | ||||
|
r5396 | |||
|
r7033 | #ifdef AT_SYMLINK_NOFOLLOW | ||
dfd = open(path, O_RDONLY); | ||||
if (dfd == -1) { | ||||
PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); | ||||
|
r7098 | goto error_value; | ||
|
r7033 | } | ||
dir = fdopendir(dfd); | ||||
#else | ||||
dir = opendir(path); | ||||
#endif | ||||
|
r5421 | if (!dir) { | ||
|
r7031 | PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); | ||
goto error_dir; | ||||
} | ||||
|
r5396 | |||
|
r5421 | list = PyList_New(0); | ||
|
r7031 | if (!list) | ||
goto error_list; | ||||
while ((ent = readdir(dir))) { | ||||
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) | ||||
continue; | ||||
|
r5416 | |||
|
r7031 | kind = entkind(ent); | ||
if (kind == -1 || keepstat) { | ||||
|
r7033 | #ifdef AT_SYMLINK_NOFOLLOW | ||
err = fstatat(dfd, ent->d_name, &st, | ||||
AT_SYMLINK_NOFOLLOW); | ||||
#else | ||||
strncpy(fullpath + pathlen + 1, ent->d_name, | ||||
PATH_MAX - pathlen); | ||||
fullpath[PATH_MAX] = 0; | ||||
err = lstat(fullpath, &st); | ||||
#endif | ||||
|
r7031 | if (err == -1) { | ||
strncpy(fullpath + pathlen + 1, ent->d_name, | ||||
PATH_MAX - pathlen); | ||||
fullpath[PATH_MAX] = 0; | ||||
PyErr_SetFromErrnoWithFilename(PyExc_OSError, | ||||
fullpath); | ||||
goto error; | ||||
} | ||||
kind = st.st_mode & S_IFMT; | ||||
} | ||||
|
r5396 | |||
|
r7034 | /* quit early? */ | ||
if (skip && kind == S_IFDIR && !strcmp(ent->d_name, skip)) { | ||||
ret = PyList_New(0); | ||||
goto error; | ||||
} | ||||
|
r7031 | if (keepstat) { | ||
stat = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL); | ||||
if (!stat) | ||||
goto error; | ||||
memcpy(&((struct listdir_stat *)stat)->st, &st, sizeof(st)); | ||||
elem = Py_BuildValue("siN", ent->d_name, kind, stat); | ||||
} else | ||||
elem = Py_BuildValue("si", ent->d_name, kind); | ||||
if (!elem) | ||||
goto error; | ||||
PyList_Append(list, elem); | ||||
Py_DECREF(elem); | ||||
} | ||||
|
r5421 | |||
|
r7031 | ret = list; | ||
Py_INCREF(ret); | ||||
|
r5421 | |||
|
r7031 | error: | ||
Py_DECREF(list); | ||||
error_list: | ||||
closedir(dir); | ||||
error_dir: | ||||
|
r7033 | #ifdef AT_SYMLINK_NOFOLLOW | ||
close(dfd); | ||||
#endif | ||||
|
r7098 | error_value: | ||
|
r7031 | return ret; | ||
|
r5421 | } | ||
|
r5396 | |||
|
r7056 | #endif /* ndef _WIN32 */ | ||
|
r7098 | static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs) | ||
{ | ||||
PyObject *statobj = NULL; /* initialize - optional arg */ | ||||
PyObject *skipobj = NULL; /* initialize - optional arg */ | ||||
char *path, *skip = NULL; | ||||
int wantstat, plen; | ||||
static char *kwlist[] = {"path", "stat", "skip", NULL}; | ||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s#|OO:listdir", | ||||
kwlist, &path, &plen, &statobj, &skipobj)) | ||||
return NULL; | ||||
wantstat = statobj && PyObject_IsTrue(statobj); | ||||
if (skipobj && skipobj != Py_None) { | ||||
skip = PyString_AsString(skipobj); | ||||
if (!skip) | ||||
return NULL; | ||||
} | ||||
return _listdir(path, plen, wantstat, skip); | ||||
} | ||||
|
r8330 | #ifdef _WIN32 | ||
static PyObject *posixfile(PyObject *self, PyObject *args, PyObject *kwds) | ||||
{ | ||||
static char *kwlist[] = {"name", "mode", "buffering", NULL}; | ||||
PyObject *file_obj = NULL; | ||||
char *name = NULL; | ||||
char *mode = "rb"; | ||||
|
r8597 | DWORD access = 0; | ||
|
r8330 | DWORD creation; | ||
HANDLE handle; | ||||
int fd, flags = 0; | ||||
int bufsize = -1; | ||||
char m0, m1, m2; | ||||
char fpmode[4]; | ||||
int fppos = 0; | ||||
int plus; | ||||
FILE *fp; | ||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:posixfile", kwlist, | ||||
Py_FileSystemDefaultEncoding, | ||||
&name, &mode, &bufsize)) | ||||
return NULL; | ||||
m0 = mode[0]; | ||||
m1 = m0 ? mode[1] : '\0'; | ||||
m2 = m1 ? mode[2] : '\0'; | ||||
plus = m1 == '+' || m2 == '+'; | ||||
fpmode[fppos++] = m0; | ||||
if (m1 == 'b' || m2 == 'b') { | ||||
flags = _O_BINARY; | ||||
fpmode[fppos++] = 'b'; | ||||
} | ||||
else | ||||
flags = _O_TEXT; | ||||
if (plus) { | ||||
flags |= _O_RDWR; | ||||
access = GENERIC_READ | GENERIC_WRITE; | ||||
fpmode[fppos++] = '+'; | ||||
} | ||||
fpmode[fppos++] = '\0'; | ||||
switch (m0) { | ||||
case 'r': | ||||
creation = OPEN_EXISTING; | ||||
if (!plus) { | ||||
flags |= _O_RDONLY; | ||||
access = GENERIC_READ; | ||||
} | ||||
break; | ||||
case 'w': | ||||
creation = CREATE_ALWAYS; | ||||
if (!plus) { | ||||
access = GENERIC_WRITE; | ||||
flags |= _O_WRONLY; | ||||
} | ||||
break; | ||||
case 'a': | ||||
creation = OPEN_ALWAYS; | ||||
flags |= _O_APPEND; | ||||
if (!plus) { | ||||
flags |= _O_WRONLY; | ||||
access = GENERIC_WRITE; | ||||
} | ||||
break; | ||||
default: | ||||
PyErr_Format(PyExc_ValueError, | ||||
"mode string must begin with one of 'r', 'w', " | ||||
"or 'a', not '%c'", m0); | ||||
goto bail; | ||||
} | ||||
handle = CreateFile(name, access, | ||||
FILE_SHARE_READ | FILE_SHARE_WRITE | | ||||
FILE_SHARE_DELETE, | ||||
NULL, | ||||
creation, | ||||
FILE_ATTRIBUTE_NORMAL, | ||||
0); | ||||
if (handle == INVALID_HANDLE_VALUE) { | ||||
PyErr_SetFromWindowsErrWithFilename(GetLastError(), name); | ||||
goto bail; | ||||
} | ||||
fd = _open_osfhandle((intptr_t) handle, flags); | ||||
if (fd == -1) { | ||||
CloseHandle(handle); | ||||
PyErr_SetFromErrnoWithFilename(PyExc_IOError, name); | ||||
goto bail; | ||||
} | ||||
fp = _fdopen(fd, fpmode); | ||||
if (fp == NULL) { | ||||
_close(fd); | ||||
PyErr_SetFromErrnoWithFilename(PyExc_IOError, name); | ||||
goto bail; | ||||
} | ||||
file_obj = PyFile_FromFile(fp, name, mode, fclose); | ||||
if (file_obj == NULL) { | ||||
fclose(fp); | ||||
goto bail; | ||||
} | ||||
PyFile_SetBufSize(file_obj, bufsize); | ||||
bail: | ||||
PyMem_Free(name); | ||||
return file_obj; | ||||
} | ||||
#endif | ||||
|
r5396 | static char osutil_doc[] = "Native operating system services."; | ||
static PyMethodDef methods[] = { | ||||
|
r5421 | {"listdir", (PyCFunction)listdir, METH_VARARGS | METH_KEYWORDS, | ||
"list a directory\n"}, | ||||
|
r8330 | #ifdef _WIN32 | ||
{"posixfile", (PyCFunction)posixfile, METH_VARARGS | METH_KEYWORDS, | ||||
"Open a file with POSIX-like semantics.\n" | ||||
"On error, this function may raise either a WindowsError or an IOError."}, | ||||
#endif | ||||
|
r5421 | {NULL, NULL} | ||
|
r5396 | }; | ||
PyMODINIT_FUNC initosutil(void) | ||||
{ | ||||
|
r5421 | if (PyType_Ready(&listdir_stat_type) == -1) | ||
return; | ||||
|
r5396 | |||
|
r5421 | Py_InitModule3("osutil", methods, osutil_doc); | ||
|
r5396 | } | ||