Skip to content

Commit d8a9d82

Browse files
committed
More refined impl of utime(s), utimensat
1 parent 6b47181 commit d8a9d82

File tree

4 files changed

+222
-74
lines changed

4 files changed

+222
-74
lines changed
Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,59 @@
1-
#include <utime.h>
1+
#include <time.h>
22
#include <stdlib.h>
33
#include <stdio.h>
4+
#include <sys/time.h>
5+
#include <fcntl.h> /* Definition of AT_* constants */
6+
#include <sys/stat.h>
7+
#include <utime.h>
8+
#include <sys/syscall.h> /* Definition of SYS_* constants */
9+
#include <unistd.h>
10+
411
int main(int argc, char **argv){
512
/*
613
struct utimbuf {
7-
time_t actime;
14+
time_t atime;
815
time_t modtime;
916
*/
10-
11-
12-
struct utimbuf * mytimebuf = (struct utimbuf*)malloc(sizeof(struct utimbuf));
13-
mytimebuf->actime = rand();
14-
mytimebuf->modtime = rand();
15-
if(argc < 2){
16-
exit(1);
17+
srand(0x1337);
18+
struct timespec utimensat_times[2];
19+
struct timespec atime;
20+
struct timespec mtime;
21+
atime.tv_sec = rand();
22+
atime.tv_nsec = rand();
23+
mtime.tv_sec = rand();
24+
mtime.tv_nsec = rand() & 0xffff; // avoid illegal arg error
25+
utimensat_times[0] = atime;
26+
utimensat_times[1] = mtime;
27+
int res = utimensat(AT_FDCWD, "./utimensat-test", utimensat_times,0);
28+
if (!res){
29+
puts("utimensat timestamp should have changed");
1730
}
18-
int res = utime(argv[1], mytimebuf);
31+
32+
33+
struct utimbuf utime_time[1];
34+
struct utimbuf actime;
35+
actime.actime = rand();
36+
actime.modtime = rand();
37+
utime_time[0] = actime;
38+
39+
res = syscall(SYS_utime,"./utime-test", utime_time);
1940
if (!res){
20-
puts("Timestamp should have changed");
41+
puts("utime timestamp should have changed");
2142
}
22-
if(mytimebuf){
23-
free(mytimebuf);
43+
44+
struct timeval utimes_times[2];
45+
struct timeval utimes_actime;
46+
struct timeval utimes_modtime;
47+
utimes_actime.tv_sec = rand() & 0xff;
48+
utimes_actime.tv_usec = rand() & 0xff;
49+
utimes_modtime.tv_sec = rand() & 0xff;
50+
utimes_modtime.tv_usec = rand() & 0xffff; // avoid illegal arg error
51+
utimes_times[0] = utimes_actime;
52+
utimes_times[1] = utimes_modtime;
53+
res = syscall(SYS_utimes,"./utimes-test", utimes_times);
54+
if (!res){
55+
puts("utimes timestamp should have changed");
2456
}
57+
58+
2559
}

qiling/os/linux/syscall.py

Lines changed: 146 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,20 @@
1212
from math import floor
1313
import ctypes
1414
import os
15+
from qiling.os.posix.const import *
1516

1617
def __get_timespec_struct(archbits: int):
17-
long = getattr(ctypes, f'c_int{archbits}')
18-
ulong = getattr(ctypes, f'c_uint{archbits}')
18+
long = getattr(ctypes, f"c_int{archbits}")
19+
ulong = getattr(ctypes, f"c_uint{archbits}")
1920

2021
class timespec(ctypes.Structure):
2122
_pack_ = archbits // 8
2223

23-
_fields_ = (
24-
('tv_sec', ulong),
25-
('tv_nsec', long)
26-
)
24+
_fields_ = (("tv_sec", ulong), ("tv_nsec", long))
2725

2826
return timespec
2927

28+
3029
def __get_timespec_obj(archbits: int):
3130
now = datetime.now().timestamp()
3231

@@ -40,17 +39,24 @@ def __get_timespec_obj(archbits: int):
4039
def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int):
4140
if ql.arch.type == QL_ARCH.X86:
4241
u_info = ql.mem.read(u_info_addr, 4 * 4)
43-
index = ql.unpack32s(u_info[0 : 4])
44-
base = ql.unpack32(u_info[4 : 8])
45-
limit = ql.unpack32(u_info[8 : 12])
42+
index = ql.unpack32s(u_info[0:4])
43+
base = ql.unpack32(u_info[4:8])
44+
limit = ql.unpack32(u_info[8:12])
4645

4746
ql.log.debug("set_thread_area base : 0x%x limit is : 0x%x" % (base, limit))
4847

4948
if index == -1:
5049
index = ql.os.gdtm.get_free_idx(12)
5150

5251
if index in (12, 13, 14):
53-
access = QL_X86_A_PRESENT | QL_X86_A_PRIV_3 | QL_X86_A_DESC_DATA | QL_X86_A_DATA | QL_X86_A_DATA_E | QL_X86_A_DATA_W
52+
access = (
53+
QL_X86_A_PRESENT
54+
| QL_X86_A_PRIV_3
55+
| QL_X86_A_DESC_DATA
56+
| QL_X86_A_DATA
57+
| QL_X86_A_DATA_E
58+
| QL_X86_A_DATA_W
59+
)
5460

5561
ql.os.gdtm.register_gdt_segment(index, base, limit, access)
5662
ql.mem.write_ptr(u_info_addr, index, 4)
@@ -59,12 +65,12 @@ def ql_syscall_set_thread_area(ql: Qiling, u_info_addr: int):
5965
return -1
6066

6167
elif ql.arch.type == QL_ARCH.MIPS:
62-
CONFIG3_ULR = (1 << 13)
68+
CONFIG3_ULR = 1 << 13
6369
ql.arch.regs.cp0_config3 = CONFIG3_ULR
6470
ql.arch.regs.cp0_userlocal = u_info_addr
6571
ql.arch.regs.v0 = 0
6672
ql.arch.regs.a3 = 0
67-
ql.log.debug ("set_thread_area(0x%x)" % u_info_addr)
73+
ql.log.debug("set_thread_area(0x%x)" % u_info_addr)
6874

6975
return 0
7076

@@ -77,83 +83,167 @@ def ql_syscall_set_tls(ql: Qiling, address: int):
7783

7884
ql.log.debug("settls(%#x)", address)
7985

86+
8087
def ql_syscall_clock_gettime(ql: Qiling, clock_id: int, tp: int):
8188
ts_obj = __get_timespec_obj(ql.arch.bits)
8289
ql.mem.write(tp, bytes(ts_obj))
8390

8491
return 0
8592

93+
8694
def ql_syscall_gettimeofday(ql: Qiling, tv: int, tz: int):
8795
if tv:
8896
ts_obj = __get_timespec_obj(ql.arch.bits)
8997
ql.mem.write(tv, bytes(ts_obj))
9098

9199
if tz:
92-
ql.mem.write(tz, b'\x00' * 8)
100+
ql.mem.write(tz, b"\x00" * 8)
93101

94102
return 0
95103

96-
'''
97-
TODO
98-
int futimens(int fd, const struct timespec times[_Nullable 2]);
99104

100-
'''
105+
"""
106+
TODO: This is considered deprecated,
107+
https://www.man7.org/linux/man-pages/man2/futimesat.2.html
108+
but should there be a wrapper added for legacy code?
109+
int futimesat(int dirfd, const char *pathname,
110+
const struct timeval times[2]);
111+
112+
"""
113+
114+
115+
# Handle seconds conversions 'in house'
116+
def microseconds_to_nanoseconds(s):
117+
return s * 1000
118+
119+
120+
def seconds_to_nanoseconds(s):
121+
return s * 1000000000
101122

102-
'''
103-
TODO
123+
124+
"""
125+
Actual implmentation of utime(s)
126+
Rather than repeat work based on different
127+
precision requirements, just convert seconds/microseconds
128+
to ns and pass to os.utime()
129+
"""
130+
131+
132+
def do_utime(ql: Qiling, filename: ctypes.POINTER, times: ctypes.POINTER, s):
133+
real_file = ""
134+
try:
135+
# get path inside of qiling rootfs
136+
real_file = ql.os.path.transform_to_real_path(ql.mem.string(filename))
137+
except Exception as ex: # return errors appropriately, don't try to handle
138+
# everything ourselves
139+
return -ex.errno
140+
actime = modtime = 0
141+
"""
142+
times[0] specifies the new access time, and times[1] specifies the new modification time.
143+
If times is NULL, then analogously to utime(), the access and modification times of the file are set to the
144+
current time.
145+
"""
146+
if s: # utimes, times[0] == new access time, times[1] == modification
147+
data = make_timeval_buf(ql.arch.bits, ql.arch.endian)
148+
with data.ref(ql.mem, times) as ref_atime: # times[0]
149+
actime = seconds_to_nanoseconds(ref_atime.tv_sec)
150+
actime += microseconds_to_nanoseconds(ref_atime.tv_usec)
151+
with data.ref(ql.mem, times + ctypes.sizeof(data)) as ref_mtime: # increment by ctypes.sizeof() to get times[1]
152+
modtime = seconds_to_nanoseconds(ref_mtime.tv_sec)
153+
modtime += microseconds_to_nanoseconds(ref_mtime.tv_usec)
154+
155+
else:
156+
# utime uses utimbuf, so different data handling needs to be done
157+
data = make_utimbuf(ql.arch.bits, ql.arch.endian)
158+
with data.ref(ql.mem, times) as ref:
159+
actime = seconds_to_nanoseconds(ref.actime)
160+
modtime = seconds_to_nanoseconds(ref.modtime)
161+
try:
162+
os.utime(real_file, ns=(actime, modtime))
163+
except Exception as ex:
164+
return -ex.errno
165+
return 0
166+
167+
168+
"""
169+
https://www.man7.org/linux/man-pages/man2/utimes.2.html
104170
int utime(const char *filename,
105171
const struct utimbuf *_Nullable times);
172+
"""
106173

107-
'''
108174

109-
'''
110-
TODO: This is considered deprecated,
111-
but should there be a wrapper added for legacy code?
112-
int futimesat(int dirfd, const char *pathname,
113-
const struct timeval times[2]);
175+
def ql_syscall_utime(ql: Qiling, filename: ctypes.POINTER, times: ctypes.POINTER):
176+
return do_utime(ql, filename, times, False) # False for 's' means
177+
# do plain utime
114178

115-
'''
116179

117-
'''
118-
TODO: int utimes(const char *filename,
180+
"""
181+
https://www.man7.org/linux/man-pages/man2/utimes.2.html
182+
int utimes(const char *filename,
119183
const struct timeval times[_Nullable 2]);
184+
"""
120185

121-
'''
122186

123-
'''
124-
sys_utimensat int dfd const char *filename struct timespec *utimes int flags
125-
'''
126-
def ql_syscall_utimensat(ql:qiling.Qiling, dfd: int, filename:POINTER, utimes: POINTER, flags: int):
127-
path = ''
128-
if dfd == AT_FDCWD:
129-
ql.emu_stop()
130-
ql.log.debug("inside hook")
131-
if filename == 0:
132-
return EACCES
133-
if utimes == 0:
134-
return EACCES
135-
ql.log.debug(hex(filename))
136-
unpacked_filename = ql.mem.string(filename)
137-
if unpacked_filename.find("/") == 0: # starts with /, assumed to be absolute path
138-
dfd = None
187+
def ql_syscall_utimes(ql: Qiling, filename: ctypes.POINTER, times: ctypes.POINTER):
188+
return do_utime(ql, filename, times, True) # True for 's' means the
189+
# we want 'utimes', which has a different prototype, and consequently,
190+
# struct unpacking requirements, then utime
191+
192+
193+
"""
194+
Not re-using the do_utime implementation so we can handle
195+
the dfd and timespec unpacking here
196+
"""
197+
198+
199+
def do_utime_fd_ns(
200+
ql: Qiling, dfd: int, filename: ctypes.POINTER, utimes: ctypes.POINTER, flags: int, symlinks
201+
):
202+
# transform to real path, which ensures that we are
203+
# operating inside of the qiling root
204+
unpacked_filename = ql.os.path.transform_to_real_path(ql.mem.string(filename))
139205
timespec_struct = make_timespec_buf(ql.arch.bits, ql.arch.endian)
140-
atime_nsec = mtime_nsec = atime_sec = mtime_sec = 0
206+
atime_nsec = mtime_nsec = 0
207+
if dfd is not None:
208+
dfd = ql.os.fd[dfd].fileno
141209
with timespec_struct.ref(ql.mem, utimes) as atime_ref:
142210
atime_nsec = atime_ref.tv_nsec
143-
atime_sec = atime_ref.tv_sec
144-
with timespec_struct.ref(ql.mem, utimes+ctypes.sizeof(timespec_struct)) as mtime_ref:
211+
atime_nsec += seconds_to_nanoseconds(atime_ref.tv_sec)
212+
with timespec_struct.ref(
213+
ql.mem, utimes + ctypes.sizeof(timespec_struct)
214+
) as mtime_ref:
145215
mtime_nsec = mtime_ref.tv_nsec
146-
mtime_sec = mtime_ref.tv_sec
147-
ql.log.debug(f"Got filename {unpacked_filename} for utime syscall ")
216+
mtime_nsec += seconds_to_nanoseconds(mtime_ref.tv_sec)
217+
ql.log.debug(f"Got filename {unpacked_filename} for utimensat syscall ")
148218
try:
149-
follow_symlink = True
150-
if flags == AT_SYMLINK_NOFOLLOW:
151-
follow_symlink = False
152-
os.utime(unpacked_filename, ns=(atime_nsec, mtime_nsec), dir_fd=dfd, follow_symlinks=follow_symlink)
153-
os.utime(unpacked_filename, (atime_sec, mtime_sec), dir_fd=dfd, follow_symlinks=follow_symlink)
219+
os.utime(
220+
unpacked_filename,
221+
ns=(atime_nsec, mtime_nsec),
222+
dir_fd=dfd,
223+
follow_symlinks=symlinks,
224+
)
154225
except Exception as ex:
155226
return -ex.errno
156227
return 0
157-
158228

159229

230+
"""
231+
https://www.man7.org/linux/man-pages/man2/utimensat.2.html
232+
sys_utimensat int dfd const char *filename struct timespec *utimes int flags
233+
"""
234+
235+
236+
def ql_syscall_utimensat(
237+
ql: Qiling, dfd: int, filename: ctypes.POINTER, utimes: ctypes.POINTER, flags: int
238+
):
239+
if filename == 0:
240+
return EACCES
241+
if utimes == 0:
242+
return EACCES
243+
if dfd == AT_FDCWD:
244+
dfd = None
245+
if flags == AT_SYMLINK_NOFOLLOW:
246+
follow_symlink = False
247+
else:
248+
follow_symlink = True
249+
return do_utime_fd_ns(ql, dfd, filename, utimes, flags, follow_symlink)

qiling/os/posix/const.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,3 +1087,6 @@ class qnx_mmap_prot_flags(QlPrettyFlag):
10871087

10881088
# fcntl
10891089
AT_SYMLINK_NOFOLLOW = 0x100
1090+
# https://docs.huihoo.com/doxygen/linux/kernel/3.7/include_2uapi_2linux_2fcntl_8h.html
1091+
AT_SYMLINK_NOFOLLOW = 0x100
1092+
AT_FDCWD = 0xffffff9c # -0n100 in 2's complement

qiling/os/posix/structs.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,29 @@ class utimbuf(Struct):
162162
def make_timespec_buf(archbits: int, endian: QL_ENDIAN):
163163
Struct = struct.get_aligned_struct(archbits, endian)
164164
class timespec(Struct):
165-
_fields_ = (
166-
('tv_sec', ctypes.c_long),
167-
('tv_nsec', ctypes.c_long)
168-
)
169-
return timespec
165+
if archbits == 32:
166+
_fields_ = (
167+
('tv_sec', ctypes.c_uint32),
168+
('tv_nsec', ctypes.c_uint32)
169+
)
170+
else:
171+
_fields_ = (
172+
('tv_sec', ctypes.c_long),
173+
('tv_nsec', ctypes.c_long)
174+
)
175+
return timespec
176+
177+
def make_timeval_buf(archbits: int, endian: QL_ENDIAN):
178+
Struct = struct.get_aligned_struct(archbits, endian)
179+
class timeval(Struct):
180+
if archbits == 64:
181+
_fields_ = (
182+
('tv_sec', ctypes.c_long),
183+
('tv_usec', ctypes.c_long)
184+
)
185+
else:
186+
_fields_ = (
187+
('tv_sec', ctypes.c_uint32),
188+
('tv_usec', ctypes.c_uint32)
189+
)
190+
return timeval

0 commit comments

Comments
 (0)