Exemple #1
0
static void CALLBACK
handle_entry_change(
    DWORD err_code,                 // completion code
    DWORD bytes_transfered,         // number of bytes transferred
    LPOVERLAPPED event_container    // I/O information buffer
) {
    WDM_PMonitorCallbackParam param;
    WDM_PQueueItem data_to_process;

    if ( err_code == ERROR_OPERATION_ABORTED ) {
        // Async operation was canceled. This shouldn't happen.
        // TODO:
        //   1. Maybe add a union in the queue for errors?
        //   2. What's the best action when this happens?
        WDM_DEBUG("Dir handler closed in the process callback!");
        return;
    }

    if ( ! bytes_transfered ) {
        WDM_DEBUG("Buffer overflow?! Changes are bigger than the buffer!");
        return;
    }

    param = (WDM_PMonitorCallbackParam)event_container->hEvent;
    data_to_process = wdm_queue_item_new(WDM_QUEUE_ITEM_TYPE_DATA);
    data_to_process->data = wdm_queue_item_data_new();

    WDM_WDEBUG("Change detected in '%s'", param->entry->user_data->dir);

    data_to_process->data->user_data = param->entry->user_data;

    // Copy change data to the backup buffer
    memcpy(data_to_process->data->buffer, param->entry->buffer, bytes_transfered);

    // Add the backup buffer to the change queue
    wdm_queue_enqueue(param->monitor->changes, data_to_process);

    // Resume watching the dir for changes
    register_monitoring_entry(param->entry);

    // Tell the processing thread to process the changes
    if ( WaitForSingleObject(param->monitor->process_event, 0) != WAIT_OBJECT_0 ) { // Check if already signaled
        SetEvent(param->monitor->process_event);
    }
}
Exemple #2
0
static VALUE
combined_watch(BOOL recursively, int argc, VALUE *argv, VALUE self)
{
    WDM_PMonitor monitor;
    WDM_PEntry entry;
    int directory_letters_count;
    VALUE directory, flags, os_encoded_directory;
    BOOL running;

    // TODO: Maybe raise a more user-friendly error?
    rb_need_block();

    Data_Get_Struct(self, WDM_Monitor, monitor);

    EnterCriticalSection(&monitor->lock);
        running = monitor->running;
    LeaveCriticalSection(&monitor->lock);

    if ( running ) {
        rb_raise(eWDM_MonitorRunningError, "You can't watch new directories while the monitor is running!");
    }

    rb_scan_args(argc, argv, "1*", &directory, &flags);

    Check_Type(directory, T_STRING);

    entry = wdm_entry_new();
    entry->user_data->watch_childeren = recursively;
    entry->user_data->callback =  rb_block_proc();
    entry->user_data->flags = RARRAY_LEN(flags) == 0 ? WDM_MONITOR_FLAGS_DEFAULT : extract_flags_from_rb_array(flags);

    // WTF Ruby source: The original code (file.c) uses the following macro to make sure that the encoding
    // of the string is ASCII-compatible, but UTF-16LE (Windows default encoding) is not!!!
    //
    // FilePathValue(directory);

    os_encoded_directory = rb_str_encode_ospath(directory);

    // RSTRING_LEN can't be used because it would return the count of bytes the string uses in its encoding (like UTF-8).
    // UTF-8 might use more than one byte for the char, which is not needed for WCHAR strings.
    // Also, the result of MultiByteToWideChar _includes_ the NULL char at the end, which is not true for RSTRING.
    //
    // Example: 'C:\Users\Maher\Desktop\تجربة' with __ENCODING__ == UTF-8
    //   MultiByteToWideChar => 29 (28-char + null)
    //   RSTRING_LEN => 33 (23-char + 10-bytes for 5 Arabic letters which take 2 bytes each)
    //
    directory_letters_count = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, NULL, 0);

    entry->user_data->dir = ALLOCA_N(WCHAR, directory_letters_count);

    MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(os_encoded_directory), -1, entry->user_data->dir, directory_letters_count);

    WDM_WDEBUG("New path to watch: '%s'", entry->user_data->dir);

    entry->user_data->dir = wdm_utils_full_pathname(entry->user_data->dir);

    if ( entry->user_data->dir == 0 ) {
        wdm_entry_free(entry);
        rb_raise(eWDM_Error, "Can't get the absolute path for the passed directory: '%s'!", RSTRING_PTR(directory));
    }

    if ( ! wdm_utils_unicode_is_directory(entry->user_data->dir) ) {
        wdm_entry_free(entry);
        rb_raise(eWDM_InvalidDirectoryError, "No such directory: '%s'!", RSTRING_PTR(directory));
    }

    entry->dir_handle = CreateFileW(
        entry->user_data->dir,     // pointer to the file name
        FILE_LIST_DIRECTORY,       // access (read/write) mode
        FILE_SHARE_READ            // share mode
            | FILE_SHARE_WRITE
            | FILE_SHARE_DELETE,
        NULL,                       // security descriptor
        OPEN_EXISTING,              // how to create
        FILE_FLAG_BACKUP_SEMANTICS
            | FILE_FLAG_OVERLAPPED, // file attributes
        NULL
    );

    if ( entry->dir_handle ==  INVALID_HANDLE_VALUE ) {
        wdm_entry_free(entry);
        rb_raise(eWDM_Error, "Can't watch directory: '%s'!", RSTRING_PTR(directory));
    }

    // Store a reference to the entry instead of an event as the event
    // won't be used when using callbacks.
    entry->event_container.hEvent = wdm_monitor_callback_param_new(monitor, entry);

    wdm_monitor_update_head(monitor, entry);

    WDM_WDEBUG("Watching directory: '%s'", entry->user_data->dir);

    return Qnil;
}
Exemple #3
0
// TODO:
//   1. this function uses a lot of 'alloca' calls, which AFAIK is not recommeneded! Can this be avoided?
//   2. all wcscat calls can be done faster with memcpy, but is it worth sacrificing the readability?
static VALUE
extract_absolute_path_from_notification(const LPWSTR base_dir, const PFILE_NOTIFY_INFORMATION info) {
    LPWSTR buffer, absolute_filepath;
    WCHAR file[_MAX_FNAME], ext[_MAX_EXT], filename[WDM_MAX_FILENAME];
    DWORD filename_len, absolute_filepath_len;
    LPSTR multibyte_filepath;
    int multibyte_filepath_buffer_size;
    VALUE path;

    filename_len = info->FileNameLength/sizeof(WCHAR);

    // The file in the 'info' struct is NOT null-terminated, so add 1 extra char to the allocation
    buffer = ALLOCA_N(WCHAR, filename_len + 1);

    memcpy(buffer, info->FileName, info->FileNameLength);

    // Null-terminate the string
    buffer[filename_len] = L'\0';

    WDM_WDEBUG("change in: '%s'", buffer);

    absolute_filepath_len = wcslen(base_dir) + filename_len;
    absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + 1); // 1 for NULL
    absolute_filepath[0] = L'\0';

    wcscat(absolute_filepath, base_dir);
    wcscat(absolute_filepath, buffer);

    WDM_WDEBUG("absolute path is: '%s'", absolute_filepath);

    _wsplitpath(buffer, NULL, NULL, file, ext);

    // TODO: Extracting the file name from 'buffer' is only needed when watching sub-dirs
    filename[0] = L'\0';
    if ( file[0] != L'\0' ) wcscat(filename, file);
    if ( ext[0]  != L'\0' ) wcscat(filename, ext);

    WDM_WDEBUG("filename: '%s'", filename);

    filename_len = wcslen(filename);

    // The maximum length of an 8.3 filename is twelve, including the dot.
    if (filename_len <= 12 && wcschr(filename, L'~'))
    {
        LPWSTR unicode_absolute_filepath;
        WCHAR absolute_long_filepath[WDM_MAX_WCHAR_LONG_PATH];
        BOOL is_unc_path;

        is_unc_path = wdm_utils_is_unc_path(absolute_filepath);

        unicode_absolute_filepath = ALLOCA_N(WCHAR, absolute_filepath_len + (is_unc_path ? 8 : 4) + 1); // 8 for "\\?\UNC\" or 4 for "\\?\", and 1 for \0

        unicode_absolute_filepath[0] = L'\0';
        wcscat(unicode_absolute_filepath, L"\\\\?\\");

        if ( is_unc_path ) {
            wcscat(unicode_absolute_filepath, L"UNC\\");
            wcscat(unicode_absolute_filepath, absolute_filepath + 2); // +2 to skip the begin of a UNC path
        }
        else {
            wcscat(unicode_absolute_filepath, absolute_filepath);
        }

        // Convert to the long filename form. Unfortunately, this
        // does not work for deletions, so it's an imperfect fix.
        if (GetLongPathNameW(unicode_absolute_filepath, absolute_long_filepath, WDM_MAX_WCHAR_LONG_PATH) != 0) {
            absolute_filepath = absolute_long_filepath + 4; // Skip first 4 pointers of "\\?\"
            absolute_filepath_len = wcslen(absolute_filepath);
            WDM_WDEBUG("Short path converted to long: '%s'", absolute_filepath);
        }
        else {
            WDM_DEBUG("Can't convert short path to long: '%s'", rb_w32_strerror(GetLastError()));
        }
    }

    // The convention in Ruby is to use forward-slashes to seprarate dirs on all platforms.
    wdm_utils_convert_back_to_forward_slashes(absolute_filepath, absolute_filepath_len + 1);

    // Convert the path from WCHAR to multibyte CHAR to use it in a ruby string
    multibyte_filepath_buffer_size =
        WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1, NULL, 0, NULL, NULL);

    multibyte_filepath = ALLOCA_N(CHAR, multibyte_filepath_buffer_size);

    if ( 0 == WideCharToMultiByte(CP_UTF8, 0, absolute_filepath, absolute_filepath_len + 1,
            multibyte_filepath, multibyte_filepath_buffer_size, NULL, NULL) ) {
        rb_raise(eWDM_Error, "Failed to add the change file path to the event!");
    }

    WDM_DEBUG("will report change in: '%s'",  multibyte_filepath);

    path = rb_enc_str_new(multibyte_filepath,
        multibyte_filepath_buffer_size - 1, // -1 because this func takes the chars count, not bytes count
        wdm_rb_enc_utf8);

    OBJ_TAINT(path);

    return path;
}