PageRenderTime 93ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/pypy/rpython/module/ll_os_stat.py

https://bitbucket.org/dac_io/pypy
Python | 460 lines | 341 code | 70 blank | 49 comment | 66 complexity | b5eb05a062799fca5a2e967c71b0eca6 MD5 | raw file
  1. """Annotation and rtyping support for the result of os.stat(), os.lstat()
  2. and os.fstat(). In RPython like in plain Python the stat result can be
  3. indexed like a tuple but also exposes the st_xxx attributes.
  4. """
  5. import os, sys
  6. from pypy.annotation import model as annmodel
  7. from pypy.tool.pairtype import pairtype
  8. from pypy.tool.sourcetools import func_with_new_name, func_renamer
  9. from pypy.rpython import extregistry
  10. from pypy.rpython.extfunc import register_external, extdef
  11. from pypy.rpython.lltypesystem import rffi, lltype
  12. from pypy.rpython.tool import rffi_platform as platform
  13. from pypy.rpython.lltypesystem.rtupletype import TUPLE_TYPE
  14. from pypy.rlib import rposix
  15. from pypy.rlib.rarithmetic import intmask
  16. from pypy.rlib.objectmodel import specialize
  17. from pypy.translator.tool.cbuild import ExternalCompilationInfo
  18. from pypy.rpython.annlowlevel import hlstr
  19. # Support for float times is here.
  20. # - ALL_STAT_FIELDS contains Float fields if the system can retrieve
  21. # sub-second timestamps.
  22. # - TIMESPEC is defined when the "struct stat" contains st_atim field.
  23. if sys.platform.startswith('linux') or sys.platform.startswith('openbsd'):
  24. TIMESPEC = platform.Struct('struct timespec',
  25. [('tv_sec', rffi.TIME_T),
  26. ('tv_nsec', rffi.LONG)])
  27. else:
  28. TIMESPEC = None
  29. # all possible fields - some of them are not available on all platforms
  30. ALL_STAT_FIELDS = [
  31. ("st_mode", lltype.Signed),
  32. ("st_ino", lltype.SignedLongLong),
  33. ("st_dev", lltype.SignedLongLong),
  34. ("st_nlink", lltype.Signed),
  35. ("st_uid", lltype.Signed),
  36. ("st_gid", lltype.Signed),
  37. ("st_size", lltype.SignedLongLong),
  38. ("st_atime", lltype.Float),
  39. ("st_mtime", lltype.Float),
  40. ("st_ctime", lltype.Float),
  41. ("st_blksize", lltype.Signed),
  42. ("st_blocks", lltype.Signed),
  43. ("st_rdev", lltype.Signed),
  44. ("st_flags", lltype.Signed),
  45. #("st_gen", lltype.Signed), -- new in CPy 2.5, not implemented
  46. #("st_birthtime", lltype.Float), -- new in CPy 2.5, not implemented
  47. ]
  48. N_INDEXABLE_FIELDS = 10
  49. # For OO backends, expose only the portable fields (the first 10).
  50. PORTABLE_STAT_FIELDS = ALL_STAT_FIELDS[:N_INDEXABLE_FIELDS]
  51. # ____________________________________________________________
  52. #
  53. # Annotation support
  54. class SomeStatResult(annmodel.SomeObject):
  55. knowntype = os.stat_result
  56. def rtyper_makerepr(self, rtyper):
  57. from pypy.rpython.module import r_os_stat
  58. return r_os_stat.StatResultRepr(rtyper)
  59. def rtyper_makekey_ex(self, rtyper):
  60. return self.__class__,
  61. def getattr(self, s_attr):
  62. assert s_attr.is_constant(), "non-constant attr name in getattr()"
  63. attrname = s_attr.const
  64. TYPE = STAT_FIELD_TYPES[attrname]
  65. return annmodel.lltype_to_annotation(TYPE)
  66. def _get_rmarshall_support_(self): # for rlib.rmarshal
  67. # reduce and recreate stat_result objects from 10-tuples
  68. # (we ignore the extra values here for simplicity and portability)
  69. def stat_result_reduce(st):
  70. return (st[0], st[1], st[2], st[3], st[4],
  71. st[5], st[6], st[7], st[8], st[9])
  72. def stat_result_recreate(tup):
  73. return make_stat_result(tup + extra_zeroes)
  74. s_reduced = annmodel.SomeTuple([annmodel.lltype_to_annotation(TYPE)
  75. for name, TYPE in PORTABLE_STAT_FIELDS])
  76. extra_zeroes = (0,) * (len(STAT_FIELDS) - len(PORTABLE_STAT_FIELDS))
  77. return s_reduced, stat_result_reduce, stat_result_recreate
  78. class __extend__(pairtype(SomeStatResult, annmodel.SomeInteger)):
  79. def getitem((s_sta, s_int)):
  80. assert s_int.is_constant(), "os.stat()[index]: index must be constant"
  81. index = s_int.const
  82. assert 0 <= index < N_INDEXABLE_FIELDS, "os.stat()[index] out of range"
  83. name, TYPE = STAT_FIELDS[index]
  84. return annmodel.lltype_to_annotation(TYPE)
  85. s_StatResult = SomeStatResult()
  86. def make_stat_result(tup):
  87. """Turn a tuple into an os.stat_result object."""
  88. positional = tup[:N_INDEXABLE_FIELDS]
  89. kwds = {}
  90. for i, name in enumerate(STAT_FIELD_NAMES[N_INDEXABLE_FIELDS:]):
  91. kwds[name] = tup[N_INDEXABLE_FIELDS + i]
  92. return os.stat_result(positional, kwds)
  93. class MakeStatResultEntry(extregistry.ExtRegistryEntry):
  94. _about_ = make_stat_result
  95. def compute_result_annotation(self, s_tup):
  96. return s_StatResult
  97. def specialize_call(self, hop):
  98. from pypy.rpython.module import r_os_stat
  99. return r_os_stat.specialize_make_stat_result(hop)
  100. # ____________________________________________________________
  101. #
  102. # RFFI support
  103. if sys.platform.startswith('win'):
  104. _name_struct_stat = '_stati64'
  105. INCLUDES = ['sys/types.h', 'sys/stat.h']
  106. else:
  107. _name_struct_stat = 'stat'
  108. INCLUDES = ['sys/types.h', 'sys/stat.h', 'unistd.h']
  109. compilation_info = ExternalCompilationInfo(
  110. # This must be set to 64 on some systems to enable large file support.
  111. #pre_include_bits = ['#define _FILE_OFFSET_BITS 64'],
  112. # ^^^ nowadays it's always set in all C files we produce.
  113. includes = INCLUDES
  114. )
  115. if TIMESPEC is not None:
  116. class CConfig_for_timespec:
  117. _compilation_info_ = compilation_info
  118. TIMESPEC = TIMESPEC
  119. TIMESPEC = lltype.Ptr(
  120. platform.configure(CConfig_for_timespec)['TIMESPEC'])
  121. def posix_declaration(try_to_add=None):
  122. global STAT_STRUCT
  123. LL_STAT_FIELDS = STAT_FIELDS[:]
  124. if try_to_add:
  125. LL_STAT_FIELDS.append(try_to_add)
  126. if TIMESPEC is not None:
  127. def _expand(lst, originalname, timespecname):
  128. for i, (_name, _TYPE) in enumerate(lst):
  129. if _name == originalname:
  130. # replace the 'st_atime' field of type rffi.DOUBLE
  131. # with a field 'st_atim' of type 'struct timespec'
  132. lst[i] = (timespecname, TIMESPEC.TO)
  133. break
  134. _expand(LL_STAT_FIELDS, 'st_atime', 'st_atim')
  135. _expand(LL_STAT_FIELDS, 'st_mtime', 'st_mtim')
  136. _expand(LL_STAT_FIELDS, 'st_ctime', 'st_ctim')
  137. del _expand
  138. else:
  139. # Replace float fields with integers
  140. for name in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'):
  141. for i, (_name, _TYPE) in enumerate(LL_STAT_FIELDS):
  142. if _name == name:
  143. LL_STAT_FIELDS[i] = (_name, lltype.Signed)
  144. break
  145. class CConfig:
  146. _compilation_info_ = compilation_info
  147. STAT_STRUCT = platform.Struct('struct %s' % _name_struct_stat, LL_STAT_FIELDS)
  148. try:
  149. config = platform.configure(CConfig, ignore_errors=
  150. try_to_add is not None)
  151. except platform.CompilationError:
  152. if try_to_add:
  153. return # failed to add this field, give up
  154. raise
  155. STAT_STRUCT = lltype.Ptr(config['STAT_STRUCT'])
  156. if try_to_add:
  157. STAT_FIELDS.append(try_to_add)
  158. # This lists only the fields that have been found on the underlying platform.
  159. # Initially only the PORTABLE_STAT_FIELDS, but more may be added by the
  160. # following loop.
  161. STAT_FIELDS = PORTABLE_STAT_FIELDS[:]
  162. if sys.platform != 'win32':
  163. posix_declaration()
  164. for _i in range(len(PORTABLE_STAT_FIELDS), len(ALL_STAT_FIELDS)):
  165. posix_declaration(ALL_STAT_FIELDS[_i])
  166. del _i
  167. # these two global vars only list the fields defined in the underlying platform
  168. STAT_FIELD_TYPES = dict(STAT_FIELDS) # {'st_xxx': TYPE}
  169. STAT_FIELD_NAMES = [_name for (_name, _TYPE) in STAT_FIELDS]
  170. del _name, _TYPE
  171. def build_stat_result(st):
  172. # only for LL backends
  173. if TIMESPEC is not None:
  174. atim = st.c_st_atim; atime = int(atim.c_tv_sec) + 1E-9 * int(atim.c_tv_nsec)
  175. mtim = st.c_st_mtim; mtime = int(mtim.c_tv_sec) + 1E-9 * int(mtim.c_tv_nsec)
  176. ctim = st.c_st_ctim; ctime = int(ctim.c_tv_sec) + 1E-9 * int(ctim.c_tv_nsec)
  177. else:
  178. atime = st.c_st_atime
  179. mtime = st.c_st_mtime
  180. ctime = st.c_st_ctime
  181. result = (st.c_st_mode,
  182. st.c_st_ino,
  183. st.c_st_dev,
  184. st.c_st_nlink,
  185. st.c_st_uid,
  186. st.c_st_gid,
  187. st.c_st_size,
  188. atime,
  189. mtime,
  190. ctime)
  191. if "st_blksize" in STAT_FIELD_TYPES: result += (st.c_st_blksize,)
  192. if "st_blocks" in STAT_FIELD_TYPES: result += (st.c_st_blocks,)
  193. if "st_rdev" in STAT_FIELD_TYPES: result += (st.c_st_rdev,)
  194. if "st_flags" in STAT_FIELD_TYPES: result += (st.c_st_flags,)
  195. return make_stat_result(result)
  196. def register_stat_variant(name, traits):
  197. if name != 'fstat':
  198. arg_is_path = True
  199. s_arg = traits.str0
  200. ARG1 = traits.CCHARP
  201. else:
  202. arg_is_path = False
  203. s_arg = int
  204. ARG1 = rffi.INT
  205. if sys.platform == 'win32':
  206. # See Win32 implementation below
  207. posix_stat_llimpl = make_win32_stat_impl(name, traits)
  208. return extdef(
  209. [s_arg], s_StatResult, traits.ll_os_name(name),
  210. llimpl=posix_stat_llimpl)
  211. if sys.platform.startswith('linux'):
  212. # because we always use _FILE_OFFSET_BITS 64 - this helps things work that are not a c compiler
  213. _functions = {'stat': 'stat64',
  214. 'fstat': 'fstat64',
  215. 'lstat': 'lstat64'}
  216. c_func_name = _functions[name]
  217. else:
  218. c_func_name = name
  219. posix_mystat = rffi.llexternal(c_func_name,
  220. [ARG1, STAT_STRUCT], rffi.INT,
  221. compilation_info=compilation_info)
  222. @func_renamer('os_%s_llimpl' % (name,))
  223. def posix_stat_llimpl(arg):
  224. stresult = lltype.malloc(STAT_STRUCT.TO, flavor='raw')
  225. try:
  226. if arg_is_path:
  227. arg = traits.str2charp(arg)
  228. error = rffi.cast(rffi.LONG, posix_mystat(arg, stresult))
  229. if arg_is_path:
  230. traits.free_charp(arg)
  231. if error != 0:
  232. raise OSError(rposix.get_errno(), "os_?stat failed")
  233. return build_stat_result(stresult)
  234. finally:
  235. lltype.free(stresult, flavor='raw')
  236. @func_renamer('os_%s_fake' % (name,))
  237. def posix_fakeimpl(arg):
  238. if s_arg == traits.str0:
  239. arg = hlstr(arg)
  240. st = getattr(os, name)(arg)
  241. fields = [TYPE for fieldname, TYPE in STAT_FIELDS]
  242. TP = TUPLE_TYPE(fields)
  243. ll_tup = lltype.malloc(TP.TO)
  244. for i, (fieldname, TYPE) in enumerate(STAT_FIELDS):
  245. val = getattr(st, fieldname)
  246. if isinstance(TYPE, lltype.Number):
  247. rffi.setintfield(ll_tup, 'item%d' % i, int(val))
  248. elif TYPE is lltype.Float:
  249. setattr(ll_tup, 'item%d' % i, float(val))
  250. else:
  251. setattr(ll_tup, 'item%d' % i, val)
  252. return ll_tup
  253. return extdef(
  254. [s_arg], s_StatResult, "ll_os.ll_os_%s" % (name,),
  255. llimpl=posix_stat_llimpl, llfakeimpl=posix_fakeimpl)
  256. def make_win32_stat_impl(name, traits):
  257. from pypy.rlib import rwin32
  258. from pypy.rpython.module.ll_win32file import make_win32_traits
  259. win32traits = make_win32_traits(traits)
  260. # The CRT of Windows has a number of flaws wrt. its stat() implementation:
  261. # - time stamps are restricted to second resolution
  262. # - file modification times suffer from forth-and-back conversions between
  263. # UTC and local time
  264. # Therefore, we implement our own stat, based on the Win32 API directly.
  265. from pypy.rpython.tool import rffi_platform as platform
  266. from pypy.translator.tool.cbuild import ExternalCompilationInfo
  267. from pypy.rlib import rwin32
  268. assert len(STAT_FIELDS) == 10 # no extra fields on Windows
  269. def attributes_to_mode(attributes):
  270. m = 0
  271. attributes = intmask(attributes)
  272. if attributes & win32traits.FILE_ATTRIBUTE_DIRECTORY:
  273. m |= win32traits._S_IFDIR | 0111 # IFEXEC for user,group,other
  274. else:
  275. m |= win32traits._S_IFREG
  276. if attributes & win32traits.FILE_ATTRIBUTE_READONLY:
  277. m |= 0444
  278. else:
  279. m |= 0666
  280. return m
  281. def attribute_data_to_stat(info):
  282. st_mode = attributes_to_mode(info.c_dwFileAttributes)
  283. st_size = make_longlong(info.c_nFileSizeHigh, info.c_nFileSizeLow)
  284. ctime, ctime_ns = FILE_TIME_to_time_t_nsec(info.c_ftCreationTime)
  285. mtime, mtime_ns = FILE_TIME_to_time_t_nsec(info.c_ftLastWriteTime)
  286. atime, atime_ns = FILE_TIME_to_time_t_nsec(info.c_ftLastAccessTime)
  287. result = (st_mode,
  288. 0, 0, 0, 0, 0,
  289. st_size,
  290. float(atime) + atime_ns * 1e-9,
  291. float(mtime) + mtime_ns * 1e-9,
  292. float(ctime) + ctime_ns * 1e-9)
  293. return make_stat_result(result)
  294. def by_handle_info_to_stat(info):
  295. # similar to the one above
  296. st_mode = attributes_to_mode(info.c_dwFileAttributes)
  297. st_size = make_longlong(info.c_nFileSizeHigh, info.c_nFileSizeLow)
  298. ctime, ctime_ns = FILE_TIME_to_time_t_nsec(info.c_ftCreationTime)
  299. mtime, mtime_ns = FILE_TIME_to_time_t_nsec(info.c_ftLastWriteTime)
  300. atime, atime_ns = FILE_TIME_to_time_t_nsec(info.c_ftLastAccessTime)
  301. # specific to fstat()
  302. st_ino = make_longlong(info.c_nFileIndexHigh, info.c_nFileIndexLow)
  303. st_nlink = info.c_nNumberOfLinks
  304. result = (st_mode,
  305. st_ino, 0, st_nlink, 0, 0,
  306. st_size,
  307. atime + atime_ns * 1e-9,
  308. mtime + mtime_ns * 1e-9,
  309. ctime + ctime_ns * 1e-9)
  310. return make_stat_result(result)
  311. def attributes_from_dir(l_path, data):
  312. filedata = lltype.malloc(win32traits.WIN32_FIND_DATA, flavor='raw')
  313. try:
  314. hFindFile = win32traits.FindFirstFile(l_path, filedata)
  315. if hFindFile == rwin32.INVALID_HANDLE_VALUE:
  316. return 0
  317. win32traits.FindClose(hFindFile)
  318. data.c_dwFileAttributes = filedata.c_dwFileAttributes
  319. rffi.structcopy(data.c_ftCreationTime, filedata.c_ftCreationTime)
  320. rffi.structcopy(data.c_ftLastAccessTime, filedata.c_ftLastAccessTime)
  321. rffi.structcopy(data.c_ftLastWriteTime, filedata.c_ftLastWriteTime)
  322. data.c_nFileSizeHigh = filedata.c_nFileSizeHigh
  323. data.c_nFileSizeLow = filedata.c_nFileSizeLow
  324. return 1
  325. finally:
  326. lltype.free(filedata, flavor='raw')
  327. def win32_stat_llimpl(path):
  328. data = lltype.malloc(win32traits.WIN32_FILE_ATTRIBUTE_DATA, flavor='raw')
  329. try:
  330. l_path = traits.str2charp(path)
  331. res = win32traits.GetFileAttributesEx(l_path, win32traits.GetFileExInfoStandard, data)
  332. errcode = rwin32.GetLastError()
  333. if res == 0:
  334. if errcode == win32traits.ERROR_SHARING_VIOLATION:
  335. res = attributes_from_dir(l_path, data)
  336. errcode = rwin32.GetLastError()
  337. traits.free_charp(l_path)
  338. if res == 0:
  339. raise WindowsError(errcode, "os_stat failed")
  340. return attribute_data_to_stat(data)
  341. finally:
  342. lltype.free(data, flavor='raw')
  343. def win32_fstat_llimpl(fd):
  344. handle = rwin32._get_osfhandle(fd)
  345. filetype = win32traits.GetFileType(handle)
  346. if filetype == win32traits.FILE_TYPE_CHAR:
  347. # console or LPT device
  348. return make_stat_result((win32traits._S_IFCHR,
  349. 0, 0, 0, 0, 0,
  350. 0, 0, 0, 0))
  351. elif filetype == win32traits.FILE_TYPE_PIPE:
  352. # socket or named pipe
  353. return make_stat_result((win32traits._S_IFIFO,
  354. 0, 0, 0, 0, 0,
  355. 0, 0, 0, 0))
  356. elif filetype == win32traits.FILE_TYPE_UNKNOWN:
  357. error = rwin32.GetLastError()
  358. if error != 0:
  359. raise WindowsError(error, "os_fstat failed")
  360. # else: unknown but valid file
  361. # normal disk file (FILE_TYPE_DISK)
  362. info = lltype.malloc(win32traits.BY_HANDLE_FILE_INFORMATION,
  363. flavor='raw', zero=True)
  364. try:
  365. res = win32traits.GetFileInformationByHandle(handle, info)
  366. if res == 0:
  367. raise WindowsError(rwin32.GetLastError(), "os_fstat failed")
  368. return by_handle_info_to_stat(info)
  369. finally:
  370. lltype.free(info, flavor='raw')
  371. if name == 'fstat':
  372. return win32_fstat_llimpl
  373. else:
  374. return win32_stat_llimpl
  375. #__________________________________________________
  376. # Helper functions for win32
  377. def make_longlong(high, low):
  378. return (rffi.r_longlong(high) << 32) + rffi.r_longlong(low)
  379. # Seconds between 1.1.1601 and 1.1.1970
  380. secs_between_epochs = rffi.r_longlong(11644473600)
  381. def FILE_TIME_to_time_t_nsec(filetime):
  382. ft = make_longlong(filetime.c_dwHighDateTime, filetime.c_dwLowDateTime)
  383. # FILETIME is in units of 100 nsec
  384. nsec = (ft % 10000000) * 100
  385. time = (ft / 10000000) - secs_between_epochs
  386. return intmask(time), intmask(nsec)
  387. def time_t_to_FILE_TIME(time, filetime):
  388. ft = rffi.r_longlong((time + secs_between_epochs) * 10000000)
  389. filetime.c_dwHighDateTime = rffi.r_uint(ft >> 32)
  390. filetime.c_dwLowDateTime = rffi.r_uint(ft) # masking off high bits