Example #1
0
posix_errno_t efile_make_hard_link(const efile_path_t *existing_path, const efile_path_t *new_path) {
    ASSERT_PATH_FORMAT(existing_path);
    ASSERT_PATH_FORMAT(new_path);

    if(!CreateHardLinkW((WCHAR*)new_path->data, (WCHAR*)existing_path->data, NULL)) {
        return windows_to_posix_errno(GetLastError());
    }

    return 0;
}
Example #2
0
posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) {
    posix_errno_t posix_errno;
    ErlNifBinary result_bin;
    DWORD attributes;

    ASSERT_PATH_FORMAT(path);

    attributes = GetFileAttributesW((WCHAR*)path->data);

    if(attributes == INVALID_FILE_ATTRIBUTES) {
        return windows_to_posix_errno(GetLastError());
    } else if(!(attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
        return EINVAL;
    }

    if(!is_name_surrogate(path)) {
        return EINVAL;
    }

    posix_errno = internal_read_link(path, &result_bin);

    if(posix_errno == 0) {
        if(!normalize_path_result(&result_bin)) {
            enif_release_binary(&result_bin);
            return ENOMEM;
        }

        (*result) = enif_make_binary(env, &result_bin);
    }

    return posix_errno;
}
Example #3
0
posix_errno_t efile_altname(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) {
    ErlNifBinary result_bin;

    ASSERT_PATH_FORMAT(path);

    if(is_path_root(path)) {
        /* Root paths can't be queried so we'll just return them as they are. */
        if(!enif_alloc_binary(path->size, &result_bin)) {
            return ENOMEM;
        }

        sys_memcpy(result_bin.data, path->data, path->size);
    } else {
        WIN32_FIND_DATAW data;
        HANDLE handle;

        WCHAR *name_buffer;
        int name_length;

        /* Reject path wildcards. */
        if(wcspbrk(&((const WCHAR*)path->data)[LP_PREFIX_LENGTH], L"?*")) {
            return ENOENT;
        }

        handle = FindFirstFileW((const WCHAR*)path->data, &data);

        if(handle == INVALID_HANDLE_VALUE) {
            return windows_to_posix_errno(GetLastError());
        }

        FindClose(handle);

        name_length = wcslen(data.cAlternateFileName);

        if(name_length > 0) {
            name_buffer = data.cAlternateFileName;
        } else {
            name_length = wcslen(data.cFileName);
            name_buffer = data.cFileName;
        }

        /* Include NUL-terminator; it will be removed after normalization. */
        name_length += 1;

        if(!enif_alloc_binary(name_length * sizeof(WCHAR), &result_bin)) {
            return ENOMEM;
        }

        sys_memcpy(result_bin.data, name_buffer, name_length * sizeof(WCHAR));
    }

    if(!normalize_path_result(&result_bin)) {
        enif_release_binary(&result_bin);
        return ENOMEM;
    }

    (*result) = enif_make_binary(env, &result_bin);

    return 0;
}
Example #4
0
posix_errno_t efile_set_cwd(const efile_path_t *path) {
    const WCHAR *path_start;

    ASSERT_PATH_FORMAT(path);

    /* We have to use _wchdir since that's the only function that updates the
     * per-drive working directory, but it naively assumes that all paths
     * starting with \\ are UNC paths, so we have to skip the long-path prefix.
     *
     * _wchdir doesn't handle long-prefixed UNC paths either so we hand those
     * to SetCurrentDirectoryW instead. The per-drive working directory is
     * irrelevant for such paths anyway. */

    if(!IS_LONG_UNC_PATH(PATH_LENGTH(path), path->data)) {
        path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;

        if(_wchdir(path_start)) {
            return windows_to_posix_errno(GetLastError());
        }
    } else {
        if(!SetCurrentDirectoryW((WCHAR*)path->data)) {
            return windows_to_posix_errno(GetLastError());
        }
    }

    return 0;
}
Example #5
0
posix_errno_t efile_make_dir(const efile_path_t *path) {
    ASSERT_PATH_FORMAT(path);

    if(!CreateDirectoryW((WCHAR*)path->data, NULL)) {
        return windows_to_posix_errno(GetLastError());
    }

    return 0;
}
Example #6
0
posix_errno_t efile_make_soft_link(const efile_path_t *existing_path, const efile_path_t *new_path) {
    DWORD link_flags;

    ASSERT_PATH_FORMAT(existing_path);
    ASSERT_PATH_FORMAT(new_path);

    if(has_file_attributes(existing_path, FILE_ATTRIBUTE_DIRECTORY)) {
        link_flags = SYMBOLIC_LINK_FLAG_DIRECTORY;
    } else {
        link_flags = 0;
    }

    if(!CreateSymbolicLinkW((WCHAR*)new_path->data, (WCHAR*)existing_path->data, link_flags)) {
        return windows_to_posix_errno(GetLastError());
    }

    return 0;
}
Example #7
0
posix_errno_t efile_list_dir(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result) {
    ERL_NIF_TERM list_head;
    WIN32_FIND_DATAW data;
    HANDLE search_handle;
    WCHAR *search_path;
    DWORD last_error;

    ASSERT_PATH_FORMAT(path);

    search_path = enif_alloc(path->size + 2 * sizeof(WCHAR));

    if(search_path == NULL) {
        return ENOMEM;
    }

    sys_memcpy(search_path, path->data, path->size);
    search_path[PATH_LENGTH(path) + 0] = L'\\';
    search_path[PATH_LENGTH(path) + 1] = L'*';
    search_path[PATH_LENGTH(path) + 2] = L'\0';

    search_handle = FindFirstFileW(search_path, &data);
    last_error = GetLastError();

    enif_free(search_path);

    if(search_handle == INVALID_HANDLE_VALUE) {
        return windows_to_posix_errno(last_error);
    }

    list_head = enif_make_list(env, 0);

    do {
        int name_length = wcslen(data.cFileName);

        if(!is_ignored_name(name_length, data.cFileName)) {
            unsigned char *name_bytes;
            ERL_NIF_TERM name_term;
            size_t name_size;

            name_size = name_length * sizeof(WCHAR);

            name_bytes = enif_make_new_binary(env, name_size, &name_term);
            sys_memcpy(name_bytes, data.cFileName, name_size);

            list_head = enif_make_list_cell(env, name_term, list_head);
        }
    } while(FindNextFileW(search_handle, &data));

    FindClose(search_handle);
    (*result) = list_head;

    return 0;
}
Example #8
0
/* Mirrors the PathIsRootW function of the shell API, but doesn't choke on
 * paths longer than MAX_PATH. */
static int is_path_root(const efile_path_t *path) {
    const WCHAR *path_start, *path_end, *path_iterator;
    int length;

    ASSERT_PATH_FORMAT(path);

    if(!IS_LONG_UNC_PATH(PATH_LENGTH(path), path->data)) {
        path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;
        length = PATH_LENGTH(path) - LP_PREFIX_LENGTH;

        /* A single \ refers to the root of the current working directory. */
        if(length == 1) {
            return IS_SLASH(path_start[0]);
        }

        /* Drive letter. */
        if(length == 3 && iswalpha(path_start[0]) && path_start[1] == L':') {
            return IS_SLASH(path_start[2]);
        }

        return 0;
    }

    /* Check whether we're a UNC root, eg. \\server, \\server\share */

    path_start = (WCHAR*)path->data + LP_UNC_PREFIX_LENGTH;
    length = PATH_LENGTH(path) - LP_UNC_PREFIX_LENGTH;

    path_end = &path_start[length];
    path_iterator = path_start;

    /* Server name must be at least one character. */
    if(length <= 1) {
        return 0;
    }

    /* Slide to the slash between the server and share names, if present. */
    while(path_iterator < path_end && !IS_SLASH(*path_iterator)) {
        path_iterator++;
    }

    /* Slide past the end of the string, stopping at the first slash we
     * encounter. */
    do {
        path_iterator++;
    }  while(path_iterator < path_end && !IS_SLASH(*path_iterator));

    /* If we're past the end of the string and it didnt't end with a slash,
     * then we're a root path. */
    return path_iterator >= path_end && !IS_SLASH(path_start[length - 1]);
}
Example #9
0
posix_errno_t efile_del_dir(const efile_path_t *path) {
    ASSERT_PATH_FORMAT(path);

    if(!RemoveDirectoryW((WCHAR*)path->data)) {
        DWORD last_error = GetLastError();

        if(last_error == ERROR_DIRECTORY) {
            return ENOTDIR;
        }

        return windows_to_posix_errno(last_error);
    }

    return 0;
}
Example #10
0
posix_errno_t efile_set_cwd(const efile_path_t *path) {
    const WCHAR *path_start;

    ASSERT_PATH_FORMAT(path);

    /* We have to use _wchdir since that's the only function that updates the
     * per-drive working directory, but it naively assumes that all paths
     * starting with \\ are UNC paths, so we have to skip the \\?\-prefix. */
    path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;

    if(_wchdir(path_start)) {
        return windows_to_posix_errno(GetLastError());
    }

    return 0;
}
Example #11
0
static posix_errno_t internal_read_link(const efile_path_t *path, 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;
    }

    actual_length = GetFinalPathNameByHandleW(link_handle,
        (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);
    }

    /* GetFinalPathNameByHandle always prepends with "\\?\" and NUL-terminates,
     * so we never have to touch-up the resulting path. */

    ASSERT_PATH_FORMAT(result);

    return 0;
}
Example #12
0
static int get_drive_number(const efile_path_t *path) {
    const WCHAR *path_start;
    int length;

    ASSERT_PATH_FORMAT(path);

    path_start = (WCHAR*)path->data + LP_PREFIX_LENGTH;
    length = PATH_LENGTH(path) - LP_PREFIX_LENGTH;

    if(length >= 2 && path_start[1] == L':') {
        WCHAR drive_letter = path_start[0];

        if(drive_letter >= L'A' && drive_letter <= L'Z') {
            return drive_letter - L'A' + 1;
        } else if(drive_letter >= L'a' && drive_letter <= L'z') {
            return drive_letter - L'a' + 1;
        }
    }

    return -1;
}
Example #13
0
posix_errno_t efile_del_file(const efile_path_t *path) {
    ASSERT_PATH_FORMAT(path);

    if(!DeleteFileW((WCHAR*)path->data)) {
        DWORD last_error = GetLastError();

        switch(last_error) {
        case ERROR_INVALID_NAME:
            /* Attempted to delete a device or similar. */
            return EACCES;
        case ERROR_ACCESS_DENIED:
            /* Windows NT reports removing a directory as EACCES instead of
             * EPERM. */
            if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) {
                return EPERM;
            }
            break;
        }

        return windows_to_posix_errno(last_error);
    }

    return 0;
}
Example #14
0
posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
        ErlNifResourceType *nif_type, efile_data_t **d) {

    DWORD attributes, access_flags, open_mode;
    HANDLE handle;

    ASSERT_PATH_FORMAT(path);

    access_flags = 0;
    open_mode = 0;

    if(modes & EFILE_MODE_READ && !(modes & EFILE_MODE_WRITE)) {
        access_flags = GENERIC_READ;
        open_mode = OPEN_EXISTING;
    } else if(modes & EFILE_MODE_WRITE && !(modes & EFILE_MODE_READ)) {
        access_flags = GENERIC_WRITE;
        open_mode = CREATE_ALWAYS;
    } else if(modes & EFILE_MODE_READ_WRITE) {
        access_flags = GENERIC_READ | GENERIC_WRITE;
        open_mode = OPEN_ALWAYS;
    } else {
        return EINVAL;
    }

    if(modes & EFILE_MODE_APPEND) {
        access_flags |= FILE_APPEND_DATA;
        open_mode = OPEN_ALWAYS;
    }

    if(modes & EFILE_MODE_EXCLUSIVE) {
        open_mode = CREATE_NEW;
    }

    if(modes & EFILE_MODE_SYNC) {
        attributes = FILE_FLAG_WRITE_THROUGH;
    } else {
        attributes = FILE_ATTRIBUTE_NORMAL;
    }

    handle = CreateFileW((WCHAR*)path->data, access_flags,
        FILE_SHARE_FLAGS, NULL, open_mode, attributes, NULL);

    if(handle != INVALID_HANDLE_VALUE) {
        efile_win_t *w;

        w = (efile_win_t*)enif_alloc_resource(nif_type, sizeof(efile_win_t));
        w->handle = handle;

        EFILE_INIT_RESOURCE(&w->common, modes);
        (*d) = &w->common;

        return 0;
    } else {
        DWORD last_error = GetLastError();

        /* Rewrite all failures on directories to EISDIR to match the old
         * driver. */
        if(has_file_attributes(path, FILE_ATTRIBUTE_DIRECTORY)) {
            return EISDIR;
        }

        return windows_to_posix_errno(last_error);
    }
}
Example #15
0
posix_errno_t efile_rename(const efile_path_t *old_path, const efile_path_t *new_path) {
    BOOL old_is_directory, new_is_directory;
    DWORD move_flags, last_error;

    ASSERT_PATH_FORMAT(old_path);
    ASSERT_PATH_FORMAT(new_path);

    move_flags = MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH;

    if(MoveFileExW((WCHAR*)old_path->data, (WCHAR*)new_path->data, move_flags)) {
        return 0;
    }

    last_error = GetLastError();

    old_is_directory = has_file_attributes(old_path, FILE_ATTRIBUTE_DIRECTORY);
    new_is_directory = has_file_attributes(new_path, FILE_ATTRIBUTE_DIRECTORY);

    switch(last_error) {
    case ERROR_SHARING_VIOLATION:
    case ERROR_ACCESS_DENIED:
        if(old_is_directory) {
            BOOL moved_into_itself;

            moved_into_itself = (old_path->size <= new_path->size) &&
                !_wcsnicmp((WCHAR*)old_path->data, (WCHAR*)new_path->data,
                    PATH_LENGTH(old_path));

            if(moved_into_itself) {
                return EINVAL;
            } else if(is_path_root(old_path)) {
                return EINVAL;
            }

            /* Renaming a directory across volumes needs to be rewritten as
             * EXDEV so that the caller can respond by simulating it with
             * copy/delete operations.
             *
             * Files are handled through MOVEFILE_COPY_ALLOWED. */
            if(!has_same_mount_point(old_path, new_path)) {
                return EXDEV;
            }
        }
        break;
    case ERROR_PATH_NOT_FOUND:
    case ERROR_FILE_NOT_FOUND:
        return ENOENT;
    case ERROR_ALREADY_EXISTS:
    case ERROR_FILE_EXISTS:
        if(old_is_directory && !new_is_directory) {
            return ENOTDIR;
        } else if(!old_is_directory && new_is_directory) {
            return EISDIR;
        } else if(old_is_directory && new_is_directory) {
            /* This will fail if the destination isn't empty. */
            if(RemoveDirectoryW((WCHAR*)new_path->data)) {
                return efile_rename(old_path, new_path);
            }

            return EEXIST;
        } else if(!old_is_directory && !new_is_directory) {
            /* This is pretty iffy; the public documentation says that the
             * operation may EACCES on some systems when either file is open,
             * which gives us room to use MOVEFILE_REPLACE_EXISTING and be done
             * with it, but the old implementation simulated Unix semantics and
             * there's a lot of code that relies on that.
             *
             * The simulation renames the destination to a scratch name to get
             * around the fact that it's impossible to open (and by extension
             * rename) a file that's been deleted while open. It has a few
             * drawbacks though;
             *
             * 1) It's not atomic as there's a small window where there's no
             *    file at all on the destination path.
             * 2) It will confuse applications that subscribe to folder
             *    changes.
             * 3) It will fail if we lack general permission to write in the
             *    same folder. */

            WCHAR *swap_path = enif_alloc(new_path->size + sizeof(WCHAR) * 64);

            if(swap_path == NULL) {
                return ENOMEM;
            } else {
                static LONGLONG unique_counter = 0;
                WCHAR *swap_path_end;

                /* We swap in the same folder as the destination to be
                 * reasonably sure that it's on the same volume. Note that
                 * we're avoiding GetTempFileNameW as it will fail on long
                 * paths. */

                sys_memcpy(swap_path, (WCHAR*)new_path->data, new_path->size);
                swap_path_end = swap_path + PATH_LENGTH(new_path);

                while(!IS_SLASH(*swap_path_end)) {
                    ASSERT(swap_path_end > swap_path);
                    swap_path_end--;
                }

                StringCchPrintfW(&swap_path_end[1], 64, L"erl-%lx-%llx.tmp",
                    GetCurrentProcessId(), unique_counter);
                InterlockedIncrement64(&unique_counter);
            }

            if(MoveFileExW((WCHAR*)new_path->data, swap_path, MOVEFILE_REPLACE_EXISTING)) {
                if(MoveFileExW((WCHAR*)old_path->data, (WCHAR*)new_path->data, move_flags)) {
                    last_error = ERROR_SUCCESS;
                    DeleteFileW(swap_path);
                } else {
                    last_error = GetLastError();
                    MoveFileW(swap_path, (WCHAR*)new_path->data);
                }
            } else {
                last_error = GetLastError();
                DeleteFileW(swap_path);
            }

            enif_free(swap_path);

            return windows_to_posix_errno(last_error);
        }

        return EEXIST;
    }

    return windows_to_posix_errno(last_error);
}