diff options
| author | John Högberg <[email protected]> | 2019-07-12 09:19:56 +0200 | 
|---|---|---|
| committer | John Högberg <[email protected]> | 2019-07-12 09:19:56 +0200 | 
| commit | fa81f1ea82b316a150eeffed861b1cdf5b357508 (patch) | |
| tree | fb91bbf2f4c457368d34889390b55e62733d051d | |
| parent | 434883dac1173297a9fd82166068d52f843b3339 (diff) | |
| parent | 924cd70f8b7cf1fa2256055af39723b24fd6238e (diff) | |
| download | otp-fa81f1ea82b316a150eeffed861b1cdf5b357508.tar.gz otp-fa81f1ea82b316a150eeffed861b1cdf5b357508.tar.bz2 otp-fa81f1ea82b316a150eeffed861b1cdf5b357508.zip  | |
Merge branch 'john/erts/merge-fd-file-info/OTP-15956'
* john/erts/merge-fd-file-info/OTP-15956:
  file: allow read_file_info on file descriptors
| -rw-r--r-- | erts/emulator/nifs/common/prim_file_nif.c | 53 | ||||
| -rw-r--r-- | erts/emulator/nifs/common/prim_file_nif.h | 1 | ||||
| -rw-r--r-- | erts/emulator/nifs/unix/unix_prim_file.c | 102 | ||||
| -rw-r--r-- | erts/emulator/nifs/win32/win_prim_file.c | 199 | ||||
| -rw-r--r-- | erts/preloaded/ebin/prim_file.beam | bin | 27984 -> 28780 bytes | |||
| -rw-r--r-- | erts/preloaded/src/prim_file.erl | 38 | ||||
| -rw-r--r-- | lib/kernel/doc/src/file.xml | 6 | ||||
| -rw-r--r-- | lib/kernel/src/file.erl | 18 | ||||
| -rw-r--r-- | lib/kernel/src/file_io_server.erl | 8 | ||||
| -rw-r--r-- | lib/kernel/src/raw_file_io_compressed.erl | 6 | ||||
| -rw-r--r-- | lib/kernel/src/raw_file_io_delayed.erl | 6 | ||||
| -rw-r--r-- | lib/kernel/src/raw_file_io_list.erl | 7 | ||||
| -rw-r--r-- | lib/kernel/test/file_SUITE.erl | 184 | 
13 files changed, 494 insertions, 134 deletions
diff --git a/erts/emulator/nifs/common/prim_file_nif.c b/erts/emulator/nifs/common/prim_file_nif.c index 9e9a14844e..5c5e9a2d30 100644 --- a/erts/emulator/nifs/common/prim_file_nif.c +++ b/erts/emulator/nifs/common/prim_file_nif.c @@ -162,6 +162,7 @@ WRAP_FILE_HANDLE_EXPORT(allocate_nif)  WRAP_FILE_HANDLE_EXPORT(advise_nif)  WRAP_FILE_HANDLE_EXPORT(get_handle_nif)  WRAP_FILE_HANDLE_EXPORT(ipread_s32bu_p32bu_nif) +WRAP_FILE_HANDLE_EXPORT(read_handle_info_nif)  static ErlNifFunc nif_funcs[] = {      /* File handle ops */ @@ -176,6 +177,7 @@ static ErlNifFunc nif_funcs[] = {      {"truncate_nif", 1, truncate_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},      {"allocate_nif", 3, allocate_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},      {"advise_nif", 4, advise_nif, ERL_NIF_DIRTY_JOB_IO_BOUND}, +    {"read_handle_info_nif", 1, read_handle_info_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},      /* Filesystem ops */      {"make_hard_link_nif", 2, make_hard_link_nif, ERL_NIF_DIRTY_JOB_IO_BOUND}, @@ -896,6 +898,26 @@ static ERL_NIF_TERM get_handle_nif_impl(efile_data_t *d, ErlNifEnv *env, int arg      return efile_get_handle(env, d);  } +static ERL_NIF_TERM build_file_info(ErlNifEnv *env, efile_fileinfo_t *info) { +    /* #file_info as declared in file.hrl */ +    return enif_make_tuple(env, 14, +        am_file_info, +        enif_make_uint64(env, info->size), +        efile_filetype_to_atom(info->type), +        efile_access_to_atom(info->access), +        enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info->a_time)), +        enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info->m_time)), +        enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info->c_time)), +        enif_make_uint(env, info->mode), +        enif_make_uint(env, info->links), +        enif_make_uint(env, info->major_device), +        enif_make_uint(env, info->minor_device), +        enif_make_uint(env, info->inode), +        enif_make_uint(env, info->uid), +        enif_make_uint(env, info->gid) +    ); +} +  static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {      posix_errno_t posix_errno; @@ -914,23 +936,20 @@ static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM a          return posix_error_to_tuple(env, posix_errno);      } -    /* #file_info as declared in file.hrl */ -    return enif_make_tuple(env, 14, -        am_file_info, -        enif_make_uint64(env, info.size), -        efile_filetype_to_atom(info.type), -        efile_access_to_atom(info.access), -        enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.a_time)), -        enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.m_time)), -        enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.c_time)), -        enif_make_uint(env, info.mode), -        enif_make_uint(env, info.links), -        enif_make_uint(env, info.major_device), -        enif_make_uint(env, info.minor_device), -        enif_make_uint(env, info.inode), -        enif_make_uint(env, info.uid), -        enif_make_uint(env, info.gid) -    ); +    return build_file_info(env, &info); +} + +static ERL_NIF_TERM read_handle_info_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { +    posix_errno_t posix_errno; +    efile_fileinfo_t info = {0}; + +    ASSERT(argc == 0); + +    if((posix_errno = efile_read_handle_info(d, &info))) { +        return posix_error_to_tuple(env, posix_errno); +    } + +    return build_file_info(env, &info);  }  static ERL_NIF_TERM set_permissions_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { diff --git a/erts/emulator/nifs/common/prim_file_nif.h b/erts/emulator/nifs/common/prim_file_nif.h index 020714a03b..28c1ea9d00 100644 --- a/erts/emulator/nifs/common/prim_file_nif.h +++ b/erts/emulator/nifs/common/prim_file_nif.h @@ -170,6 +170,7 @@ int efile_close(efile_data_t *d, posix_errno_t *error);  /* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */  posix_errno_t efile_read_info(const efile_path_t *path, int follow_link, efile_fileinfo_t *result); +posix_errno_t efile_read_handle_info(efile_data_t *d, efile_fileinfo_t *result);  /** @brief Sets the file times to the given values. Refer to efile_fileinfo_t   * for a description of each. */ diff --git a/erts/emulator/nifs/unix/unix_prim_file.c b/erts/emulator/nifs/unix/unix_prim_file.c index 20021b9358..176a9318b2 100644 --- a/erts/emulator/nifs/unix/unix_prim_file.c +++ b/erts/emulator/nifs/unix/unix_prim_file.c @@ -627,6 +627,33 @@ int efile_truncate(efile_data_t *d) {      return 1;  } +static void build_file_info(struct stat *data, efile_fileinfo_t *result) { +    if(S_ISCHR(data->st_mode) || S_ISBLK(data->st_mode)) { +        result->type = EFILE_FILETYPE_DEVICE; +    } else if(S_ISDIR(data->st_mode)) { +        result->type = EFILE_FILETYPE_DIRECTORY; +    } else if(S_ISREG(data->st_mode)) { +        result->type = EFILE_FILETYPE_REGULAR; +    } else if(S_ISLNK(data->st_mode)) { +        result->type = EFILE_FILETYPE_SYMLINK; +    } else { +        result->type = EFILE_FILETYPE_OTHER; +    } + +    result->a_time = (Sint64)data->st_atime; +    result->m_time = (Sint64)data->st_mtime; +    result->c_time = (Sint64)data->st_ctime; +    result->size = data->st_size; + +    result->major_device = data->st_dev; +    result->minor_device = data->st_rdev; +    result->links = data->st_nlink; +    result->inode = data->st_ino; +    result->mode = data->st_mode; +    result->uid = data->st_uid; +    result->gid = data->st_gid; +} +  posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_fileinfo_t *result) {      struct stat data; @@ -640,30 +667,7 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_          }      } -    if(S_ISCHR(data.st_mode) || S_ISBLK(data.st_mode)) { -        result->type = EFILE_FILETYPE_DEVICE; -    } else if(S_ISDIR(data.st_mode)) { -        result->type = EFILE_FILETYPE_DIRECTORY; -    } else if(S_ISREG(data.st_mode)) { -        result->type = EFILE_FILETYPE_REGULAR; -    } else if(S_ISLNK(data.st_mode)) { -        result->type = EFILE_FILETYPE_SYMLINK; -    } else { -        result->type = EFILE_FILETYPE_OTHER; -    } - -    result->a_time = (Sint64)data.st_atime; -    result->m_time = (Sint64)data.st_mtime; -    result->c_time = (Sint64)data.st_ctime; -    result->size = data.st_size; - -    result->major_device = data.st_dev; -    result->minor_device = data.st_rdev; -    result->links = data.st_nlink; -    result->inode = data.st_ino; -    result->mode = data.st_mode; -    result->uid = data.st_uid; -    result->gid = data.st_gid; +    build_file_info(&data, result);  #ifndef NO_ACCESS      result->access = EFILE_ACCESS_NONE; @@ -682,6 +686,56 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_      return 0;  } +static int check_access(struct stat *st) { +    int ret = EFILE_ACCESS_NONE; + +    if(st->st_uid == getuid()) { +        if(st->st_mode & S_IRUSR) { +            ret |= EFILE_ACCESS_READ; +        } +        if(st->st_mode & S_IWUSR) { +            ret |= EFILE_ACCESS_WRITE; +        } +        return ret; +    } + +    if(st->st_gid == getgid()) { +        if(st->st_mode & S_IRGRP) { +            ret |= EFILE_ACCESS_READ; +        } +        if(st->st_mode & S_IWGRP) { +            ret |= EFILE_ACCESS_WRITE; +        } +        return ret; +    } + +    if(st->st_mode & S_IROTH) { +        ret |= EFILE_ACCESS_READ; +    } +    if(st->st_mode & S_IWOTH) { +        ret |= EFILE_ACCESS_WRITE; +    } +    return ret; +} + +posix_errno_t efile_read_handle_info(efile_data_t *d, efile_fileinfo_t *result) { +    struct stat data; +    efile_unix_t *u = (efile_unix_t*)d; + +#ifdef HAVE_FSTAT +    if(fstat(u->fd, &data) < 0) { +        return errno; +    } + +    build_file_info(&data, result); +    result->access = check_access(&data); + +    return 0; +#else +    return ENOTSUP; +#endif +} +  posix_errno_t efile_set_permissions(const efile_path_t *path, Uint32 permissions) {      const mode_t MUTABLE_MODES = (S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO);      mode_t new_modes = permissions & MUTABLE_MODES; diff --git a/erts/emulator/nifs/win32/win_prim_file.c b/erts/emulator/nifs/win32/win_prim_file.c index 13306104c0..839ac3ea6e 100644 --- a/erts/emulator/nifs/win32/win_prim_file.c +++ b/erts/emulator/nifs/win32/win_prim_file.c @@ -773,6 +773,71 @@ static int is_name_surrogate(const efile_path_t *path) {       return result;  } +static void build_file_info(BY_HANDLE_FILE_INFORMATION *native_file_info, const efile_path_t *path, int is_link, efile_fileinfo_t *result) { +    DWORD attributes; + +    attributes = native_file_info->dwFileAttributes; + +    if(is_link) { +        result->type = EFILE_FILETYPE_SYMLINK; +        /* This should be _S_IFLNK, but the old driver always set +         * non-directories to _S_IFREG. */ +        result->mode |= _S_IFREG; +    } else if(attributes & FILE_ATTRIBUTE_DIRECTORY) { +        result->type = EFILE_FILETYPE_DIRECTORY; +        result->mode |= _S_IFDIR | _S_IEXEC; +    } else { +        if(is_executable_file(path)) { +            result->mode |= _S_IEXEC; +        } + +        result->type = EFILE_FILETYPE_REGULAR; +        result->mode |= _S_IFREG; +    } + +    if(!(attributes & FILE_ATTRIBUTE_READONLY)) { +        result->access = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE; +        result->mode |= _S_IREAD | _S_IWRITE; +    } else { +        result->access = EFILE_ACCESS_READ; +        result->mode |= _S_IREAD; +    } + +    /* Propagate user mode-bits to group/other fields */ +    result->mode |= (result->mode & 0700) >> 3; +    result->mode |= (result->mode & 0700) >> 6; + +    result->size = +        ((Uint64)native_file_info->nFileSizeHigh << 32ull) | +        (Uint64)native_file_info->nFileSizeLow; +    result->links = MAX(1, native_file_info->nNumberOfLinks); + +    result->major_device = get_drive_number(path); +    result->minor_device = 0; +    result->inode = 0; +    result->uid = 0; +    result->gid = 0; +} + +static void build_file_info_times(BY_HANDLE_FILE_INFORMATION *native_file_info, efile_fileinfo_t *result) { +    FILETIME_TO_EPOCH(result->m_time, native_file_info->ftLastWriteTime); +    FILETIME_TO_EPOCH(result->a_time, native_file_info->ftLastAccessTime); +    FILETIME_TO_EPOCH(result->c_time, native_file_info->ftCreationTime); + +    if(result->m_time == -EPOCH_DIFFERENCE) { +        /* Default to 1970 just like the old driver. */ +        result->m_time = 0; +    } + +    if(result->a_time == -EPOCH_DIFFERENCE) { +        result->a_time = result->m_time; +    } + +    if(result->c_time == -EPOCH_DIFFERENCE) { +        result->c_time = result->m_time; +    } +} +  posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_fileinfo_t *result) {      BY_HANDLE_FILE_INFORMATION native_file_info;      DWORD attributes; @@ -792,23 +857,41 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_              return windows_to_posix_errno(last_error);          } -        attributes = FILE_ATTRIBUTE_DIRECTORY; +        native_file_info.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;      } else if(is_path_root(path)) {          /* Local (or mounted) roots can be queried with GetFileAttributesW but           * lack support for GetFileInformationByHandle, so we'll skip that           * part. */ +        native_file_info.dwFileAttributes = attributes;      } else {          HANDLE handle; +        DWORD last_error; +        DWORD flags;          if(attributes & FILE_ATTRIBUTE_REPARSE_POINT) {              is_link = is_name_surrogate(path);          } +        flags = FILE_FLAG_BACKUP_SEMANTICS; +        if(!follow_links && is_link) { +            flags |= FILE_FLAG_OPEN_REPARSE_POINT; +        } + +        handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ, +            FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, flags, NULL); +        last_error = GetLastError(); + +        if(handle == INVALID_HANDLE_VALUE) { +            return windows_to_posix_errno(last_error); +        } +          if(follow_links && is_link) {              posix_errno_t posix_errno;              efile_path_t resolved_path; -            posix_errno = internal_read_link(path, &resolved_path); +            posix_errno = internal_read_link(handle, &resolved_path); + +            CloseHandle(handle);              if(posix_errno != 0) {                  return posix_errno; @@ -817,74 +900,43 @@ posix_errno_t efile_read_info(const efile_path_t *path, int follow_links, efile_              return efile_read_info(&resolved_path, 0, result);          } -        handle = CreateFileW((const WCHAR*)path->data, GENERIC_READ, -            FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, -            NULL); - -        /* The old driver never cared whether this succeeded. */ -        if(handle != INVALID_HANDLE_VALUE) { -            GetFileInformationByHandle(handle, &native_file_info); -            CloseHandle(handle); -        } +        GetFileInformationByHandle(handle, &native_file_info); +        CloseHandle(handle); -        FILETIME_TO_EPOCH(result->m_time, native_file_info.ftLastWriteTime); -        FILETIME_TO_EPOCH(result->a_time, native_file_info.ftLastAccessTime); -        FILETIME_TO_EPOCH(result->c_time, native_file_info.ftCreationTime); +        build_file_info_times(&native_file_info, result); +    } -        if(result->m_time == -EPOCH_DIFFERENCE) { -            /* Default to 1970 just like the old driver. */ -            result->m_time = 0; -        } +    build_file_info(&native_file_info, path, is_link, result); -        if(result->a_time == -EPOCH_DIFFERENCE) { -            result->a_time = result->m_time; -        } +    return 0; +} -        if(result->c_time == -EPOCH_DIFFERENCE) { -            result->c_time = result->m_time; -        } -    } +posix_errno_t efile_read_handle_info(efile_data_t *d, efile_fileinfo_t *result) { +    efile_win_t *w = (efile_win_t*)d; +    HANDLE handle; +    BY_HANDLE_FILE_INFORMATION native_file_info; +    posix_errno_t posix_errno; +    efile_path_t path; +    int length; -    if(is_link) { -        result->type = EFILE_FILETYPE_SYMLINK; -        /* This should be _S_IFLNK, but the old driver always set -         * non-directories to _S_IFREG. */ -        result->mode |= _S_IFREG; -    } else if(attributes & FILE_ATTRIBUTE_DIRECTORY) { -        result->type = EFILE_FILETYPE_DIRECTORY; -        result->mode |= _S_IFDIR | _S_IEXEC; -    } else { -        if(is_executable_file(path)) { -            result->mode |= _S_IEXEC; -        } +    sys_memset(&native_file_info, 0, sizeof(native_file_info)); -        result->type = EFILE_FILETYPE_REGULAR; -        result->mode |= _S_IFREG; +    posix_errno = internal_read_link(w->handle, &path); +    if(posix_errno != 0) { +        return posix_errno;      } -    if(!(attributes & FILE_ATTRIBUTE_READONLY)) { -        result->access = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE; -        result->mode |= _S_IREAD | _S_IWRITE; +    if(GetFileInformationByHandle(w->handle, &native_file_info)) { +        build_file_info_times(&native_file_info, result); +    } else if(is_path_root(&path)) { +        /* GetFileInformationByHandle is not supported on path roots, so +         * fall back to efile_read_info. */ +        return efile_read_info(&path, 0, result);      } else { -        result->access = EFILE_ACCESS_READ; -        result->mode |= _S_IREAD; +        return windows_to_posix_errno(GetLastError());      } -    /* Propagate user mode-bits to group/other fields */ -    result->mode |= (result->mode & 0700) >> 3; -    result->mode |= (result->mode & 0700) >> 6; - -    result->size = -        ((Uint64)native_file_info.nFileSizeHigh << 32ull) | -        (Uint64)native_file_info.nFileSizeLow; - -    result->links = MAX(1, native_file_info.nNumberOfLinks); - -    result->major_device = get_drive_number(path); -    result->minor_device = 0; -    result->inode = 0; -    result->uid = 0; -    result->gid = 0; +    build_file_info(&native_file_info, &path, 0, result);      return 0;  } @@ -963,31 +1015,20 @@ posix_errno_t efile_set_time(const efile_path_t *path, Sint64 a_time, Sint64 m_t      return windows_to_posix_errno(last_error);  } -static posix_errno_t internal_read_link(const efile_path_t *path, efile_path_t *result) { +static posix_errno_t internal_read_link(HANDLE link_handle, efile_path_t *result) {      DWORD required_length, actual_length; -    HANDLE link_handle;      DWORD last_error; -    link_handle = CreateFileW((WCHAR*)path->data, GENERIC_READ, -        FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); -    last_error = GetLastError(); - -    if(link_handle == INVALID_HANDLE_VALUE) { -        return windows_to_posix_errno(last_error); -    } -      required_length = GetFinalPathNameByHandleW(link_handle, NULL, 0, 0);      last_error = GetLastError();      if(required_length <= 0) { -        CloseHandle(link_handle);          return windows_to_posix_errno(last_error);      }      /* Unlike many other path functions (eg. GetFullPathNameW), this one       * includes the NUL terminator in its required length. */      if(!enif_alloc_binary(required_length * sizeof(WCHAR), result)) { -        CloseHandle(link_handle);          return ENOMEM;      } @@ -995,8 +1036,6 @@ static posix_errno_t internal_read_link(const efile_path_t *path, efile_path_t *          (WCHAR*)result->data, required_length, 0);      last_error = GetLastError(); -    CloseHandle(link_handle); -      if(actual_length == 0 || actual_length >= required_length) {          enif_release_binary(result);          return windows_to_posix_errno(last_error); @@ -1014,6 +1053,8 @@ posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_      posix_errno_t posix_errno;      ErlNifBinary result_bin;      DWORD attributes; +    HANDLE handle; +    DWORD last_error;      ASSERT_PATH_FORMAT(path); @@ -1029,7 +1070,17 @@ posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_          return EINVAL;      } -    posix_errno = internal_read_link(path, &result_bin); +    handle = CreateFileW((WCHAR*)path->data, GENERIC_READ, +        FILE_SHARE_FLAGS, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, +        NULL); +    last_error = GetLastError(); + +    if(handle == INVALID_HANDLE_VALUE) { +        return windows_to_posix_errno(last_error); +    } +    posix_errno = internal_read_link(handle, &result_bin); + +    CloseHandle(handle);      if(posix_errno == 0) {          if(!normalize_path_result(&result_bin)) { diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex a2c5f2f336..0152a6091f 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index 1aa5d85c64..1982424191 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -34,6 +34,7 @@  -export([read_link/1, read_link_all/1,           read_link_info/1, read_link_info/2,           read_file_info/1, read_file_info/2, +         read_handle_info/1, read_handle_info/2,           write_file_info/2, write_file_info/3]).  -export([list_dir/1, list_dir_all/1]). @@ -497,6 +498,8 @@ get_handle_nif(_FileRef) ->      erlang:nif_error(undef).  delayed_close_nif(_FileRef) ->      erlang:nif_error(undef). +read_handle_info_nif(_FileRef) -> +    erlang:nif_error(undef).  %%  %% Quality-of-life helpers @@ -598,20 +601,37 @@ read_link_info(Name, Opts) ->  read_info_1(Name, FollowLinks, TimeType) ->      try          case read_info_nif(encode_path(Name), FollowLinks) of -            {error, Reason} -> -                {error, Reason}; -            FileInfo -> -                CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType), -                MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType), -                ATime = from_posix_seconds(FileInfo#file_info.atime, TimeType), -                {ok, FileInfo#file_info{ ctime = CTime, -                                         mtime = MTime, -                                         atime = ATime }} +            {error, Reason} -> {error, Reason}; +            FileInfo -> {ok, adjust_times(FileInfo, TimeType)} +        end +    catch +        error:_ -> {error, badarg} +    end. + +read_handle_info(Fd) -> +  read_handle_info_1(Fd, local). +read_handle_info(Fd, Opts) -> +  read_handle_info_1(Fd, proplist_get_value(time, Opts, local)). + +read_handle_info_1(Fd, TimeType) -> +    try +        #{ handle := FRef } = get_fd_data(Fd), +        case read_handle_info_nif(FRef) of +            {error, Reason} -> {error, Reason}; +            FileInfo -> {ok, adjust_times(FileInfo, TimeType)}          end      catch          error:_ -> {error, badarg}      end. +adjust_times(FileInfo, TimeType) -> +    CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType), +    MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType), +    ATime = from_posix_seconds(FileInfo#file_info.atime, TimeType), +    FileInfo#file_info{ ctime = CTime, +                        mtime = MTime, +                        atime = ATime }. +  write_file_info(Filename, Info) ->      write_file_info_1(Filename, Info, local).  write_file_info(Filename, Info, Opts) -> diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index b3e8149cc2..d923207f9f 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -1444,7 +1444,11 @@ f.txt:  {person, "kalle", 25}.             break this module's atomicity guarantees as it can race with a             concurrent call to             <seealso marker="#write_file_info/2"><c>write_file_info/1,2</c> -           </seealso></p> +           </seealso>.</p> +        <p>This option has no effect when the function is +           given an I/O device instead of a file name. Use +           <seealso marker="#open/2"><c>open/2</c></seealso> with the +           <c>raw</c> mode to obtain a file descriptor first.</p>  	  <note>            <p>As file times are stored in POSIX time on most OS, it is faster to  	  query file information with option <c>posix</c>.</p> diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl index a0616da670..2ad2df97a8 100644 --- a/lib/kernel/src/file.erl +++ b/lib/kernel/src/file.erl @@ -239,20 +239,30 @@ make_dir(Name) ->  del_dir(Name) ->      check_and_call(del_dir, [file_name(Name)]). --spec read_file_info(Filename) -> {ok, FileInfo} | {error, Reason} when -      Filename :: name_all(), +-spec read_file_info(File) -> {ok, FileInfo} | {error, Reason} when +      File :: name_all() | io_device(),        FileInfo :: file_info(),        Reason :: posix() | badarg. +read_file_info(IoDevice) +  when is_pid(IoDevice); is_record(IoDevice, file_descriptor) -> +    read_file_info(IoDevice, []); +  read_file_info(Name) ->      check_and_call(read_file_info, [file_name(Name)]). --spec read_file_info(Filename, Opts) -> {ok, FileInfo} | {error, Reason} when -      Filename :: name_all(), +-spec read_file_info(File, Opts) -> {ok, FileInfo} | {error, Reason} when +      File :: name_all() | io_device(),        Opts :: [file_info_option()],        FileInfo :: file_info(),        Reason :: posix() | badarg. +read_file_info(IoDevice, Opts) when is_pid(IoDevice), is_list(Opts) -> +    file_request(IoDevice, {read_handle_info, Opts}); + +read_file_info(#file_descriptor{module = Module} = Handle, Opts) when is_list(Opts) -> +    Module:read_handle_info(Handle, Opts); +  read_file_info(Name, Opts) when is_list(Opts) ->      Args = [file_name(Name), Opts],      case check_args(Args) of diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl index 34d5497a4a..c03fbb548a 100644 --- a/lib/kernel/src/file_io_server.erl +++ b/lib/kernel/src/file_io_server.erl @@ -314,6 +314,14 @@ file_request(truncate,  	Reply ->  	    std_reply(Reply, State)      end; +file_request({read_handle_info, Opts}, +             #state{handle=Handle}=State) -> +    case ?CALL_FD(Handle, read_handle_info, [Opts]) of +        {error,Reason}=Reply -> +            {stop,Reason,Reply,State}; +        Reply -> +            {reply,Reply,State} +    end;  file_request(Unknown,   	     #state{}=State) ->      Reason = {request, Unknown}, diff --git a/lib/kernel/src/raw_file_io_compressed.erl b/lib/kernel/src/raw_file_io_compressed.erl index d5ab042d25..66c5621dd1 100644 --- a/lib/kernel/src/raw_file_io_compressed.erl +++ b/lib/kernel/src/raw_file_io_compressed.erl @@ -21,7 +21,8 @@  -export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,           position/2, write/2, pwrite/2, pwrite/3, -         read_line/1, read/2, pread/2, pread/3]). +         read_line/1, read/2, pread/2, pread/3, +         read_handle_info/2]).  %% OTP internal.  -export([ipread_s32bu_p32bu/3, sendfile/8]). @@ -118,6 +119,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->  sendfile(_,_,_,_,_,_,_,_) ->      {error, enotsup}. +read_handle_info(Fd, Opts) -> +    wrap_call(Fd, [Opts]). +  wrap_call(Fd, Command) ->      {_Owner, Pid} = get_fd_data(Fd),      try gen_statem:call(Pid, Command, infinity) of diff --git a/lib/kernel/src/raw_file_io_delayed.erl b/lib/kernel/src/raw_file_io_delayed.erl index d2ad7550a1..766467437e 100644 --- a/lib/kernel/src/raw_file_io_delayed.erl +++ b/lib/kernel/src/raw_file_io_delayed.erl @@ -23,7 +23,8 @@  -export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,           position/2, write/2, pwrite/2, pwrite/3, -         read_line/1, read/2, pread/2, pread/3]). +         read_line/1, read/2, pread/2, pread/3, +         read_handle_info/2]).  %% OTP internal.  -export([ipread_s32bu_p32bu/3, sendfile/8]). @@ -304,6 +305,9 @@ ipread_s32bu_p32bu(Fd, Offset, MaxSize) ->  sendfile(_,_,_,_,_,_,_,_) ->      {error, enotsup}. +read_handle_info(Fd, Opts) -> +    wrap_call(Fd, [Opts]). +  wrap_call(Fd, Command) ->      #{ pid := Pid } = get_fd_data(Fd),      try gen_statem:call(Pid, Command, infinity) of diff --git a/lib/kernel/src/raw_file_io_list.erl b/lib/kernel/src/raw_file_io_list.erl index 2e16e63f0e..e4fe434e13 100644 --- a/lib/kernel/src/raw_file_io_list.erl +++ b/lib/kernel/src/raw_file_io_list.erl @@ -21,7 +21,8 @@  -export([close/1, sync/1, datasync/1, truncate/1, advise/4, allocate/3,           position/2, write/2, pwrite/2, pwrite/3, -         read_line/1, read/2, pread/2, pread/3]). +         read_line/1, read/2, pread/2, pread/3, +         read_handle_info/2]).  %% OTP internal.  -export([ipread_s32bu_p32bu/3, sendfile/8]). @@ -126,3 +127,7 @@ sendfile(Fd, Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags) ->      Args = [Dest, Offset, Bytes, ChunkSize, Headers, Trailers, Flags],      PrivateFd = Fd#file_descriptor.data,      ?CALL_FD(PrivateFd, sendfile, Args). + +read_handle_info(Fd, Opts) -> +    PrivateFd = Fd#file_descriptor.data, +    ?CALL_FD(PrivateFd, read_handle_info, [Opts]). diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl index 21aaefa654..9efe83d8b3 100644 --- a/lib/kernel/test/file_SUITE.erl +++ b/lib/kernel/test/file_SUITE.erl @@ -58,6 +58,8 @@  -export([ file_info_basic_file/1, file_info_basic_directory/1,  	  file_info_bad/1, file_info_times/1, file_write_file_info/1,            file_wfi_helpers/1]). +-export([ file_handle_info_basic_file/1, file_handle_info_basic_directory/1, +	  file_handle_info_times/1]).  -export([rename/1, access/1, truncate/1, datasync/1, sync/1,  	 read_write/1, pread_write/1, append/1, exclusive/1]).  -export([ e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]). @@ -153,7 +155,10 @@ groups() ->       {pos, [], [pos1, pos2, pos3]},       {file_info, [],        [file_info_basic_file, file_info_basic_directory, -       file_info_bad, file_info_times, file_write_file_info, +       file_info_bad, file_info_times, +       file_handle_info_basic_file, file_handle_info_basic_directory, +       file_handle_info_times, +       file_write_file_info,         file_wfi_helpers]},       {consult, [], [consult1, path_consult]},       {eval, [], [eval1, path_eval]}, @@ -1417,7 +1422,8 @@ file_info_basic_directory(Config) when is_list(Config) ->          {win32, _} ->              test_directory("/", read_write),              test_directory("c:/", read_write), -            test_directory("c:\\", read_write); +            test_directory("c:\\", read_write), +            test_directory("\\\\localhost\\c$", read_write);          _ ->              test_directory("/", read)      end, @@ -1549,6 +1555,180 @@ filter_atime(Atime, Config) ->  	    Atime      end. +%% Test read_file_info on I/O devices. + +file_handle_info_basic_file(Config) when is_list(Config) -> +    RootDir = proplists:get_value(priv_dir, Config), + +    %% Create a short file. +    Name = filename:join(RootDir, +			 atom_to_list(?MODULE) +			 ++"_basic_test.fil"), +    {ok,Fd1} = ?FILE_MODULE:open(Name, write), +    io:put_chars(Fd1, "foo bar"), +    ok = ?FILE_MODULE:close(Fd1), + +    %% Test that the file has the expected attributes. +    %% The times are tricky, so we will save them to a separate test case. + +    {ok, Fd} = ?FILE_MODULE:open(Name, read), +    {ok,FileInfo} = ?FILE_MODULE:read_file_info(Fd), +    ok = ?FILE_MODULE:close(Fd), + +    {ok, FdRaw} = ?FILE_MODULE:open(Name, [read, raw]), +    {ok,FileInfoRaw} = ?FILE_MODULE:read_file_info(FdRaw), +    ok = ?FILE_MODULE:close(FdRaw), + +    #file_info{size=Size,type=Type,access=Access, +	       atime=AccessTime,mtime=ModifyTime} = FileInfo = FileInfoRaw, +    io:format("Access ~p, Modify ~p", [AccessTime, ModifyTime]), +    Size = 7, +    Type = regular, +    read_write = Access, +    true = abs(time_dist(filter_atime(AccessTime, Config), +			 filter_atime(ModifyTime, +				      Config))) < 5, +    all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)), + +    [] = flush(), +    ok. + +file_handle_info_basic_directory(Config) when is_list(Config) -> +    %% Note: filename:join/1 removes any trailing slash, +    %% which is essential for ?FILE_MODULE:file_info/1 to work on +    %% platforms such as Windows95. +    RootDir = filename:join([proplists:get_value(priv_dir, Config)]), + +    %% Test that the RootDir directory has the expected attributes. +    test_directory_handle(RootDir, read_write), + +    %% Note that on Windows file systems, +    %% "/" or "c:/" are *NOT* directories. +    %% Therefore, test that ?FILE_MODULE:file_info/1 behaves as if they were +    %% directories. +    case os:type() of +        {win32, _} -> +            test_directory_handle("/", read_write), +            test_directory_handle("c:/", read_write), +            test_directory_handle("c:\\", read_write), +            test_directory_handle("\\\\localhost\\c$", read_write); +        _ -> +            test_directory_handle("/", read) +    end, +    ok. + +test_directory_handle(Name, ExpectedAccess) -> +    {ok, DirFd} = file:open(Name, [read, directory]), +    try +        {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd), +        {ok,FileInfo} = ?FILE_MODULE:read_file_info(DirFd, [raw]), +        #file_info{size=Size,type=Type,access=Access, +                   atime=AccessTime,mtime=ModifyTime} = FileInfo, +        io:format("Testing directory ~s", [Name]), +        io:format("Directory size is ~p", [Size]), +        io:format("Access ~p", [Access]), +        io:format("Access time ~p; Modify time~p", +                  [AccessTime, ModifyTime]), +        Type = directory, +        Access = ExpectedAccess, +        all_integers(tuple_to_list(AccessTime) ++ tuple_to_list(ModifyTime)), +        [] = flush(), +        ok +    after +        file:close(DirFd) +    end. + +%% Test that the file times behave as they should. + +file_handle_info_times(Config) when is_list(Config) -> +    %% We have to try this twice, since if the test runs across the change +    %% of a month the time diff calculations will fail. But it won't happen +    %% if you run it twice in succession. +    test_server:m_out_of_n( +      1,2, +      fun() -> file_handle_info_int(Config) end), +    ok. + +file_handle_info_int(Config) -> +    %% Note: filename:join/1 removes any trailing slash, +    %% which is essential for ?FILE_MODULE:file_info/1 to work on +    %% platforms such as Windows95. + +    RootDir = filename:join([proplists:get_value(priv_dir, Config)]), +    io:format("RootDir = ~p", [RootDir]), + +    Name = filename:join(RootDir, +			 atom_to_list(?MODULE) +			 ++"_file_info.fil"), +    {ok,Fd1} = ?FILE_MODULE:open(Name, write), +    io:put_chars(Fd1,"foo"), +    {ok,FileInfo1} = ?FILE_MODULE:read_file_info(Fd1), +    ok = ?FILE_MODULE:close(Fd1), + +    {ok,Fd1Raw} = ?FILE_MODULE:open(Name, [read, raw]), +    {ok,FileInfo1Raw} = ?FILE_MODULE:read_file_info(Fd1Raw), +    ok = ?FILE_MODULE:close(Fd1Raw), + +    %% We assert that everything but the size is the same, on some OSs the +    %% size may not have been flushed to disc and we do not want to do a +    %% sync to force it. +    FileInfo1Raw = FileInfo1#file_info{ size = FileInfo1Raw#file_info.size }, + +    #file_info{type=regular,atime=AccTime1,mtime=ModTime1} = FileInfo1, + +    Now = erlang:localtime(), %??? +    io:format("Now ~p",[Now]), +    io:format("Open file Acc ~p Mod ~p",[AccTime1,ModTime1]), +    true = abs(time_dist(filter_atime(Now, Config), +			 filter_atime(AccTime1, +				      Config))) < 8, +    true = abs(time_dist(Now,ModTime1)) < 8, + +    %% Sleep until we can be sure the seconds value has changed. +    %% Note: FAT-based filesystem (like on Windows 95) have +    %% a resolution of 2 seconds. +    timer:sleep(2200), + +    %% close the file, and watch the modify date change + +    {ok,Fd2} = ?FILE_MODULE:open(Name, read), +    {ok,FileInfo2} = ?FILE_MODULE:read_file_info(Fd2), +    ok = ?FILE_MODULE:close(Fd2), + +    {ok,Fd2Raw} = ?FILE_MODULE:open(Name, [read, raw]), +    {ok,FileInfo2Raw} = ?FILE_MODULE:read_file_info(Fd2Raw), +    ok = ?FILE_MODULE:close(Fd2Raw), + +    #file_info{size=Size,type=regular,access=Access, +               atime=AccTime2,mtime=ModTime2} = FileInfo2 = FileInfo2Raw, +    io:format("Closed file Acc ~p Mod ~p",[AccTime2,ModTime2]), +    true = time_dist(ModTime1,ModTime2) >= 0, + +    %% this file is supposed to be binary, so it'd better keep it's size +    Size = 3, +    Access = read_write, + +    %% Do some directory checking + +    {ok,Fd3} = ?FILE_MODULE:open(RootDir, [read, directory]), +    {ok,FileInfo3} = ?FILE_MODULE:read_file_info(Fd3), +    ok = ?FILE_MODULE:close(Fd3), + +    {ok,Fd3Raw} = ?FILE_MODULE:open(RootDir, [read, directory, raw]), +    {ok,FileInfo3Raw} = ?FILE_MODULE:read_file_info(Fd3Raw), +    ok = ?FILE_MODULE:close(Fd3Raw), + +    #file_info{size=DSize,type=directory,access=DAccess, +               atime=AccTime3,mtime=ModTime3} = FileInfo3 = FileInfo3Raw, +    %% this dir was modified only a few secs ago +    io:format("Dir Acc ~p; Mod ~p; Now ~p", [AccTime3, ModTime3, Now]), +    true = abs(time_dist(Now,ModTime3)) < 5, +    DAccess = read_write, +    io:format("Dir size is ~p",[DSize]), + +    [] = flush(), +    ok. +  %% Test the write_file_info/2 function.  file_write_file_info(Config) when is_list(Config) ->  | 
