Преобразование вызова библиотеки Windows в POSIX для совместимости с Linux

В настоящее время у меня есть этот фрагмент кода, который работает в Windows, но мне было интересно, как сделать его совместимым с Linux (возможно, с использованием POSIX): я использую QB64.

REM Example library call written in QB64 for Windows
DECLARE LIBRARY
    FUNCTION GetFileAttributes& (f$)
    FUNCTION SetFileAttributes& (f$, BYVAL a&)
END DECLARE
DIM ASCIIZ AS STRING * 260
DIM Attribute AS LONG
Filename$ = "TESTFILE.DAT"
IF _FILEEXISTS(Filename$) THEN
    ASCIIZ = Filename$ + CHR$(0)
    Attribute = GetFileAttributes(ASCIIZ)
    Attribute = Attribute OR &H01 ' set read-only bit
    x = SetFileAttributes&(ASCIIZ, Attribute)
    IF x = 0 THEN PRINT "Error." ELSE PRINT "Success."
END IF

В настоящее время я использую этот код для окон:

' detect operating system
$IF WIN = 0 THEN
    COLOR 15, 0
    CLS
    PRINT "Sorry, this program only works in Windows.."
    END
$END IF

person eoredson    schedule 10.05.2018    source источник


Ответы (1)


POSIX имеет три основных набора разрешений:

  • пользователь: владелец файла,
  • группа: группа файла (часто основная группа, к которой принадлежит владелец файла, но не всегда), и
  • другой (также известный как "мир"): любой человек, не входящий в группу файла, но не являющийся владельцем файла.

Предполагая, что вам нужно поведение, подобное Windows, вы захотите установить для них все только для чтения (т.е. удалить все разрешения на запись); это то же самое, что WINE делает в Linux. Это не так просто в QB64 из-за того, сколько POSIX оставляет за реализацией (например, где st_mode появляется в struct stat), поэтому вам нужно написать заголовок DECLARE LIBRARY, который позволит вам обернуть функциональность на основе C, если вы этого хотите. для работы как в Linux, так и в OS X (и в любой другой системе, на которой может работать QB64):

#define _XOPEN_SOURCE 700
#include <sys/stat.h> // chmod, stat

// Make a file read-only for all users.
// Returns 0 on success, -1 on error.
int makeReadOnlyPOSIX(const char *path)
{
    int n;
    struct stat fileInfo;
    const mode_t noWriteMask = (
        S_IRUSR | S_IRGRP | S_IROTH
        | S_IXUSR | S_IXGRP | S_IXOTH
        | S_ISUID | S_ISGID
#ifdef S_ISVTX
        | S_ISVTX
#endif
    );

    n = stat(path, &fileInfo);
    if (n == -1) return n;

    n = chmod(path, fileInfo.st_mode & noWriteMask);

    return n;
}

Однако ваш исходный код QB64 также имеет недостаток: вы проверяете существование файла, а затем изменяете атрибуты файла, что является определением Уязвимость TOCTOU (см. Пример 2, примерный эквивалент вашего кода в POSIX C).

Чтобы решить проблему, просто получите и установите права доступа к файлу. Если в какой-то момент возникла проблема, вы можете либо убедиться, что файл существует, и распечатать сообщение об ошибке (или распечатать, что его не существует), либо просто распечатать сообщение об ошибке, независимо от того, существует файл или нет. Чтобы сделать код чище в Windows (поскольку GetFileAttributes& может возвращать значение ошибки), вы можете создать функцию C, которая делает то же самое, что и makeReadOnlyPOSIX:

#include <windows.h>

int makeReadOnlyWindows(const char *path)
{
    DWORD attrs;
    attrs = GetFileAttributesA(path);
    if (attrs == INVALID_FILE_ATTRIBUTES)
        return -1;
    return (int)SetFileAttributesA(path, attrs & 0x01) - 1;
}

Затем вы можете написать это в своем коде QB64:

$IF WIN = 0 THEN
IF makeReadOnlyPOSIX(ASCIIZ) = 0 THEN
$ELSE
IF makeReadOnlyWindows(ASCIIZ) = 0 THEN
$END IF
    PRINT "Success."
ELSE
    PRINT "Error."
END IF

Конечно, я предполагаю, что $IF ... THEN, $ELSE и т. д. в QB64 работают как препроцессор C (т. е. генерируют правильный вывод на основе оценок условий), но даже если это так, вы можете предпочесть что-то более похожее на это:

$IF WIN = 0 THEN
    IF makeReadOnlyPOSIX(ASCIIZ) = 0 THEN
        PRINT "Success."
    ELSE
        PRINT "Error."
    END IF
$ELSE
    IF makeReadOnlyWindows(ASCIIZ) = 0 THEN
        PRINT "Success."
    ELSE
        PRINT "Error."
    END IF
$END IF

Изменить

Если вы хотите, чтобы ваш код работал с тем же именем функции, вы можете сделать в QB64 следующее:

$IF WIN = 0 THEN
    DECLARE LIBRARY "posixHeader"
        FUNCTION makeReadOnly ALIAS makeReadOnlyPOSIX& (fname$)
    END DECLARE
$ELSE
    DECLARE LIBRARY "winHeader"
        FUNCTION makeReadOnly ALIAS makeReadOnlyWindows& (fname$)
    END DECLARE
$END IF

Таким образом, вы можете просто использовать что-то вроде следующего в своем реальном коде:

IF makeReadOnly(ASCIIZ) = 0 THEN
    PRINT "Success."
ELSE
    PRINT "Error."
END IF

Я упоминаю об этом только потому, что легче поддерживать что-то, где логика не разделена между директивами прекомпиляции, не говоря уже об отсутствии дублирования.

person Community    schedule 23.06.2018
comment
Каким будет объявление функции POSIX в QB64? - person eoredson; 25.06.2018
comment
Да, это будет FUNCTION makeReadOnlyPOSIX& (filename$) внутри блока DECLARE LIBRARY "headerName". - person ; 25.06.2018
comment
@eoredson Я добавил в свой ответ некоторую информацию об объявлениях DECLARE LIBRARY, а также предложил использовать ключевое слово ALIAS в объявлениях функций, чтобы сделать код, который вы на самом деле используете, намного проще для понимания. Конечно, это требует, чтобы обе функции вели себя одинаково (т. е. при успехе/ошибке возвращались одинаковые значения), что я и сделал намеренно. - person ; 25.06.2018