add getdents64 syscall

This commit is contained in:
tehzz 2020-05-15 21:06:15 -04:00
parent 113271122f
commit b348da6e00
2 changed files with 50 additions and 68 deletions

View File

@ -54,5 +54,7 @@ struct target_dirent64 {
abi_ushort d_reclen; abi_ushort d_reclen;
char d_name[1]; char d_name[1];
}; };
/* size of struct target_dirent without the name array */
#define TARGET_DIRENT64_LEN (offsetof(struct target_dirent64, d_name))
#endif #endif

View File

@ -10893,7 +10893,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
} else { } else {
ret = bytes_used; ret = bytes_used;
} }
unlock_user(target_dirp, arg2, ret); unlock_user(target_dirp, arg2, count);
// Don't close the `DIR *` pointer, as that will close the fd // Don't close the `DIR *` pointer, as that will close the fd
// which was used in `fdopendir` // which was used in `fdopendir`
// closedir(dirp); // closedir(dirp);
@ -10910,83 +10910,63 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
#ifdef TARGET_NR_ngetdents64 #ifdef TARGET_NR_ngetdents64
case TARGET_NR_ngetdents64: case TARGET_NR_ngetdents64:
#endif #endif
#if defined(TARGET_NR_getdents64) && defined(__NR_getdents64) #ifdef TARGET_NR_getdents64
case TARGET_NR_getdents64: case TARGET_NR_getdents64:
#if defined TARGET_ABI_IRIX || defined TARGET_ABI_SOLARIS
{ {
struct target_dirent64 *target_dirp; struct target_dirent64 *target_dirp;
struct linux_dirent64 *dirp; DIR *dirp;
abi_long dir_fd = arg1;
abi_long count = arg3; abi_long count = arg3;
dirp = malloc(count); dirp = fdopendir(dir_fd);
if (!dirp) { if (!dirp) {
ret = -TARGET_ENOMEM; ret = -host_to_target_errno(errno);
goto fail; goto fail;
} }
ret = get_errno(sys_getdents64(arg1, dirp, count)); // collect readdir calls into getdents dirp * buffer
if (!is_error(ret)) { target_dirp = lock_user(VERIFY_WRITE, arg2, count, 0);
struct linux_dirent64 *de; if (!target_dirp) {
struct target_dirent64 *tde; goto efault;
int len = ret; }
int reclen, treclen;
int count1, tnamelen;
count1 = 0; struct dirent *de;
de = dirp; struct target_dirent64 *tde = target_dirp;
if (!(target_dirp = lock_user(VERIFY_WRITE, arg2, count, 0))) uint bytes_used = 0;
goto efault; uint target_reclen;
tde = target_dirp; uint name_len;
while (len > 0) {
reclen = de->d_reclen; errno = 0;
tnamelen = reclen - offsetof(struct linux_dirent64, d_name); while ((de = readdir(dirp))) {
tnamelen = strnlen(de->d_name, tnamelen) + 1; // check if new entry will overflow the target buffer
assert(tnamelen > 0); name_len = de->d_namlen + 1;
treclen = tnamelen + offsetof(struct target_dirent64, d_name); target_reclen = TARGET_DIRENT64_LEN + name_len;
treclen = QEMU_ALIGN_UP(treclen, sizeof(abi_llong)); // is this the correct way to align this structure..?
/* XXX: avoid buffer overflow, for the price of lost entries */ target_reclen = QEMU_ALIGN_UP(target_reclen, __alignof(struct target_dirent64));
if (count1 + treclen > count) if (bytes_used + target_reclen > count) {
break; break;
__put_user(treclen, &tde->d_reclen); }
// translate from host to target dirent
__put_user(de->d_ino, &tde->d_ino); __put_user(de->d_ino, &tde->d_ino);
__put_user(de->d_off, &tde->d_off); __put_user(de->d_seekoff, &tde->d_off);
memcpy(tde->d_name, de->d_name, tnamelen); __put_user(target_reclen, &tde->d_reclen);
de = (struct linux_dirent64 *)((char *)de + reclen); memcpy(tde->d_name, de->d_name, name_len);
len -= reclen; // advance pointer in target space
tde = (struct target_dirent64 *)((char *)tde + treclen); // host pointer is moved by calling readdir
count1 += treclen; tde = (struct target_dirent64 *)((char *)tde + target_reclen);
bytes_used += target_reclen;
} }
ret = count1;
unlock_user(target_dirp, arg2, ret); if (errno != 0) {
ret = -host_to_target_errno(errno);
} else {
ret = bytes_used;
} }
free(dirp); unlock_user(target_dirp, arg2, count);
// Don't close the `DIR *` pointer, as that will close the fd
// which was used in `fdopendir`
// closedir(dirp);
} }
#else
{
struct linux_dirent64 *dirp;
abi_long count = arg3;
if (!(dirp = lock_user(VERIFY_WRITE, arg2, count, 0)))
goto efault;
ret = get_errno(sys_getdents64(arg1, dirp, count));
if (!is_error(ret)) {
struct linux_dirent64 *de;
int len = ret;
int reclen;
de = dirp;
while (len > 0) {
reclen = de->d_reclen;
if (reclen > len)
break;
__put_user(reclen, &de->d_reclen);
tswap64s((uint64_t *)&de->d_ino);
tswap64s((uint64_t *)&de->d_off);
de = (struct linux_dirent64 *)((char *)de + reclen);
len -= reclen;
}
}
unlock_user(dirp, arg2, ret);
}
#endif
#ifdef TARGET_NR_ngetdents64 #ifdef TARGET_NR_ngetdents64
if (ret >= 0 && num == TARGET_NR_ngetdents64) { if (ret >= 0 && num == TARGET_NR_ngetdents64) {
abi_long *p = lock_user(VERIFY_WRITE, arg4, sizeof(abi_long), 0); abi_long *p = lock_user(VERIFY_WRITE, arg4, sizeof(abi_long), 0);