I currently have this piece of code that works in Windows but was wondering how to make it compatible with Linux (possibly using POSIX): I am using 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
I am currently using this code for windows:
' detect operating system
$IF WIN = 0 THEN
COLOR 15, 0
CLS
PRINT "Sorry, this program only works in Windows.."
END
$END IF
POSIX has three basic permission sets:
Assuming you're wanting Windows-like behavior, you'll want to set them all to read-only (i.e. remove all write permissions); this is the same thing that WINE does on Linux. This isn't straightforward in QB64 due to how much POSIX leaves up to the implementation (e.g. where st_mode
appears in struct stat
), so you'd want to write a DECLARE LIBRARY
header that allows you to wrap the C-based functionality if you want it to work on both Linux and OS X (and any other system that QB64 can natively run on):
#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;
}
Your original QB64 code, however, also has a flaw: you're checking for existence of the file, then modifying the file's attributes, which is the definition of a TOCTOU vulnerability (see Example 2, the POSIX C approximate equivalent of your code).
To fix the problem, just get and set the file permissions. If there's a problem at any point, you can either verify the file exists and print an error message (or print that it doesn't exist), or you can simply print an error message, regardless of whether the file exists or not. To make the code cleaner in Windows (since GetFileAttributes&
can return an error value), you might create a C function that does the same as 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;
}
Then you can write this in your QB64 code:
$IF WIN = 0 THEN
IF makeReadOnlyPOSIX(ASCIIZ) = 0 THEN
$ELSE
IF makeReadOnlyWindows(ASCIIZ) = 0 THEN
$END IF
PRINT "Success."
ELSE
PRINT "Error."
END IF
Of course, I'm assuming that $IF ... THEN
, $ELSE
, etc. in QB64 work like the C preprocessor (i.e. generating correct output based on the evaluations of the conditions), but even if it does, you might prefer something more like this:
$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
Edit
If you wanted your code to work with the same function name, you could do the following in 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
That way, you can just use something like the following in your actual code:
IF makeReadOnly(ASCIIZ) = 0 THEN
PRINT "Success."
ELSE
PRINT "Error."
END IF
I mention this only because it's easier to maintain something where the logic isn't split between pre-compilation directives, not to mention the lack of duplication.