Aros/Developer/Docs/Libraries/DOS
Contents |
[edit] Introduction
A filesystem controls the way files and folders are laid out on the disk, and handles all the details of creating, reading, writing and deleting those files. Without a filesystem, your disk (or floppy, CD-ROM, USB key or SD card) is just a giant chunk of raw unstructured data.
When you access a file, a file handler is created each time.
struct FileHandle
{
/* The next three are used with packet-based filesystems */
struct Message * fh_Link; /* exec message containing packet */
struct MsgPort * fh_Port; /* packet reply port */
struct MsgPort * fh_Type; /* port to send packets to */
UBYTE * fh_Buf;
UBYTE * fh_Pos;
UBYTE * fh_End;
#ifdef AROS_DOS_PACKETS
LONG fh_Funcs;
#define fh_Func1 fh_Funcs
LONG fh_Func2;
LONG fh_Func3;
LONG fh_Args;
#define fh_Arg1 fh_Args
LONG fh_Arg2;
/* kept here until things stabilize */
ULONG fh_Size;
ULONG fh_Flags;
#else
/* The following four fields have different names and a different function than their AmigaOS equivalents.
The original names were: fh_Funcs/fh_Func1, fh_Func2, fh_Func3, fh_Args/fh_Arg1 and fh_Arg2 */
ULONG fh_Size;
ULONG fh_Flags; /* see below */
/* This is a pointer to a filesystem handler. See <dos/filesystems.h> for more information. */
struct Device * fh_Device;
/* SDuvan: Added this and removed the #if below. This field allows us to emulate packets --
specifically it makes it possible to implement the ***Pkt() functions */
SIPTR fh_CompatibilityHack;
/* A private pointer to a device specific filehandle structure. See <dos/filesystems.h> for more information. */
struct Unit * fh_Unit;
#endif
};
The only fields which are referred to by external code are fh_Device and fh_Unit. They are used for sending direct IOFS requests. In fact fh_Size and fh_Flags can also be aliased to fh_Funcs and fh_Func2, but this would affect m68k port if they decide to implement these callbacks for some reason. It might be better if fields that might hold pointers were upgraded to SIPRs rather than APTRs. This will give better source-level compatiblity with AmigaOS code, avoiding compiler warnings for example.
Previous implementation, for non AROS_DOS_PACKET case field fh_Size was at offset 24 and field fh_Flags was at offset 28. After your change, field fh_Size is at offset 44 and field fh_Flags is at offset 48. Above the FH structure before and after the change for reference.
[edit] Files
[edit] Open Close Files
Before you can do anything with a file you have to ask the operating system to "open it". This is because AROS needs to know what you are going to do with the file. Are you going to create a new file (MODE_NEWFILE) or are you going to work with an already opened file (MODE_OLDFILE). You open a file by calling the function Open():
For example, if you want to create a new file called "Highscore.dat" you write:
/* Try to open the file: */ file_handle = Open("Highscore.dat", MODE_NEWFILE ); /* Check if we have opened the file: */ if( file_handle == NULL ) /* Could NOT open file! */
Once you have finished reading/writing the file you need to close it. You do it by calling the function Close():
Close(file_handle);
Remember to close ALL files you have opened!
void main( void ) { FILE *fp; if( !open_file("text.txt", fp) ) // error occurred { printf("\n\nError opening file...\n\n"); exit(0);// not strictly needed } else { /* nice lump of code here */ fclose( fp ); // always close the FILE pointer after use } } bool open_file( char *filename, FILE *fp ) { fp = fopen( filename, "rb" ); // rb = mode read binary if( fp != NULL ) return true; else return false; }
The use of any global FILE pointers are asking for trouble, so its best not to use them. just declare their use in each function parsing if needed. global variables are a bad idea and will likely get you in trouble in the future if you get into the habit of using them without thinking about it. Make sure you understand variable scopes and their consequences before going on.
In the above example, there is no pre-defined bool type in C (there is in C++, and many C libraries define a bool or BOOL type, but it's not built into C), also no true or false. Also this will just overwrite a local copy of the file pointer with the result of fopen, with no effect on the variable outside that function whatsoever. If you want to use an output parameter like that in C (in C++ there are references), you have to pass a pointer to the type you want to overwrite and then dereference it when assigning.
int open_file( char *filename, FILE **fp ) { /* [...] */ *fp = fopen( filename, "rb" ); /* rb = mode read binary */ /* [...] */ }
In C parameters are always passed by value.
[edit] Read, Write and Seek Files
Once you have successfully opened a file you can start to read or write. AmigaDOS(TM) and AROS consider files to be a stream of bytes, and when you read or write something you need to specify how many bytes you want to read or write.
For example, if you want to read a file which contains an array (highscore) of ten integers, you write:
/* file_handle, buffer, count */
bytes_read = Read( file_handle, highscore, sizeof( highscore ) );
if( bytes_read != sizeof( highscore ) )
/* ERROR while reading! */
When you what to write to a file you use the function Write():
For example, if you want to save an array (highscore) of ten integers to a file, you simply write:
/* file_handle, buffer, count */
bytes_written = Write( file_handle, highscore, sizeof( highscore ) );
if( bytes_written != sizeof( highscore ) )
/* ERROR while writing! */
See here also.
#include "dos/dosextens.h" extern struct FileHandle *Open() main() { struct FileHandle *file_handle; file_handle = Open("CON:10/10/500/150/New Window", MODE_NEWFILE); Write (file_handle,"Hello World\n",13); Delay(400); Close(file_handle); }
currentposition=Seek(file_handle,0, OFFSET_END); currentposition=Seek(file_handle,0, OFFSET_CURRENT); currentposition=Seek(file_handle,20, OFFSET_CURRENT); currentposition=Seek(file_handle,0, OFFSET_BEGINNING);
boolean_status=WaitForChar(file_handle,waiting_time);
[edit] Protect Files And Directories
You can protect files and directory from being accidentally deleted or changed. You do it by calling the function SetProtection() which will alter the protection bits as desired in the FIB FileInfoBlock struct.
#include <proto/dos.h> #include <stdio.h> int main(void) { TEXT buffer[512]; BPTR fh = Open("__TEST__", MODE_NEWFILE); if (fh != NULL) { if (NameFromFH(fh, buffer, 512)) { printf("got name: %s\n", buffer); } else { printf("namefromlock failed. ioerr = %ld\n", IoErr()); } Close(fh); DeleteFile("__TEST__"); } else { printf("couldn't create file\n"); } return 0; }
#include <proto/dos.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { char c; if (argc < 2) { fprintf(stderr, "usage: %s <varname>\n", argv[0]); return 20; } if (GetVar(argv[1], &c, 1, GVF_BINARY_VAR) == 0) { LONG len = IoErr(); char *buf = malloc(len + 1); if (!buf) { PrintFault(ERROR_NO_FREE_STORE, argv[0]); return 20; } printf("IoErr() says the len of the value of the var '%s' is: %ld\n", argv[1], len); len = GetVar(argv[1], buf, len+1, GVF_BINARY_VAR); printf("GetVar() says the len of the value of the var '%s' is: %ld - its value is '%s'\n", argv[1], len, buf); free(buf); return 0; } PrintFault(IoErr(), argv[1]); return 20; }
[edit] Examine Files or Directories
You can examine a file or directory with the function Examine(). You give the function a pointer to a "lock" on the file or directory you want to examine, and it will initializes a FileInfoBlock structure for you.
Is it safe to use memory allocated with AllocMem with the dos function Examine and use fib_SIZEOF as the size, or is it better to use AllocDosObject instead? AllocMem is safe for all currently existing AmigaOS versions but AllocDosObject is better for future compatibility. Note that AllocDosObject does not exist in Kickstart 1.3 and below, so for these OS versions you need to use AllocMem.
struct FileLock {
BPTR fl_Link;
LONG fl_Key;
LONG fl_Access;
APTR fl_Task;
BPTR fl_Volume;
LABEL fl_SIZEOF;
};
SHARED_LOCK = -2 EXCLUSIVE_LOCK = -1 ACCESS_READ = SHARED_LOCK ACCESS_WRITE = EXCLUSIVE_LOCK
#include <dos/dos.h> #include <proto/dos.h> #include <dos/datetime.h> int main(void) { struct DateTime curr; char day[LEN_DATSTRING]; char time[LEN_DATSTRING]; char date[LEN_DATSTRING]; struct DateStamp stamp; curr.dat_Format = FORMAT_DOS; curr.dat_Flags = 0; curr.dat_StrDay = day; curr.dat_StrDate = date; curr.dat_StrTime = time; DateStamp(&curr.dat_Stamp); DateToStr(&curr); Printf("Current time: %s, %s, %s\n", day, date, time); BPTR fh = Open("__TEST__", MODE_NEWFILE); if (fh != NULL) { struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL); if (fib != NULL) { if (ExamineFH(fh, fib)) { curr.dat_Stamp = fib->fib_Date; DateToStr(&curr); Printf("File modification time: %s, %s, %s\n", day, date, time); } else PrintFault(IoErr(), "Examine failed"); Printf("Waiting 5 seconds\n"); Delay(5*50); DateStamp(&stamp); Printf("Calling SetFileDate\n"); if(SetFileDate("__TEST__", &stamp)) { if (ExamineFH(fh, fib)) { curr.dat_Stamp = fib->fib_Date; DateToStr(&curr); Printf("New file modification time: %s, %s, %s\n", day, date, time); } else PrintFault(IoErr(), "Examine failed"); } else PrintFault(IoErr(), "SetFileDate"); FreeDosObject(DOS_FIB, fib); } else PrintFault(IoErr(), "Couldn't alloc FileInfoBlock"); Close(fh); DeleteFile("__TEST__"); } else PrintFault(IoErr(), "Couldn't create file"); return 0; }
[edit] Names
The FileInfoBlock look like this:
struct FileInfoBlock { LONG fib_DiskKey; LONG fib_DirEntryType; char fib_FileName[108]; LONG fib_Protection; LONG fib_EntryType; LONG fib_Size; LONG fib_NumBlocks; struct DateStamp fib_Date; char fib_Comment[80]; char fib_Reserved[36]; };
*fib_DiskKey: Key number for the disk. Usually of no interest for us.
*fib_DirEntryType: If the number is smaller than zero it is a file. On the other hand, if the number is larger than zero it is a directory.
*fib_FileName: Null terminated string containing the filename. (Do not use longer filenames than 30 characters.) FileInfoBlock fib_Comment and fib_FileName should be BCPL strings would make porting of ftp-handler easier and Staf wrote that BCPL strings will become BCPL strings on all platforms.
*fib_Protection: Field containing the protection flags:
FIBF_DELETE : the file/directory can not be deleted.
FIBF_EXECUTE : the file can not be executed.
FIBF_WRITE : you can not write to the file.
FIBF_READ : you can not read the file.
FIBF_ARCHIVE : Archive bit.
FIBF_PURE : Pure bit.
FIBF_SCRIPT : Script bit.
(Note! All of the flags are for the moment not working!)
*fib_EntryType: File/Directory entry type number. Usually of no interest for us.
*fib_Size: Size of the file (in bytes).
*fib_NumBlocks: Number of blocks in the file.
*fib_Date: Structure containing the date when the file was latest updated/created.
*fib_Comment: Null terminated string containing the file comment. (Max 80 characters including the NULL sign.)
The Amiga Dos/Examine() docs say that the buffers are ASCIIZ when Dos/Examine() returns. The only confusion was what the *handler* fills in the FIB on an ACTION_EXAMINE_OBJECT. On m68k it was always BCPL-style. There was some idea that on non-m68k handlers should use ASCIIZ, to save the BCPL2ASCIIZ step in Dos/Examine(). The *handler* always fills in as BCPL style, and Dos/Examine() always runs a BCPL2ASCIIZ conversion on fib_Comment and fib_FileName *regardless* of architecture. Especially since a length-terminated string is quicker to convert to a null-terminated string than the other way around. Only handler writers need to be aware of these details.
*fib_Reserved: This field is for the moment reserved, and may therefore not be used.
#include <dos/dos.h> #include <proto/dos.h> #include <stdio.h> int main(void) { BPTR fh = Open("__TEST__", MODE_NEWFILE); if (fh != NULL) { struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL); if (fib != NULL) { if (ExamineFH(fh, fib)) { printf("got fib. filename = %s\n", fib->fib_FileName); } else { printf("examinefh failed, ioerr = %ld\n", IoErr()); } FreeDosObject(DOS_FIB, fib); } else { printf("couldn't allocate fileinfoblock\n"); } Close(fh); DeleteFile("__TEST__"); } else { printf("couldn't create file\n"); } return 0; }
The DateStamp structure look like this:
struct DateStamp
{
LONG ds_Days; /* days since 01-Jan-1978 */
LONG ds_Minute; /* minutes past midnight */
LONG ds_Tick; /* ticks past the last minute */
};
flock calls DupLockFromFH() with filehandle that is opened in MODE_NEWFILE mode. It is not allowed to be duplicated. Can be fixed by replacing O_CREAT = NEWFILE with following: first create the file with MODE_NEWFILE, close it and then re-open it with MODE_OLDFILE. ChangeMode() is also possibility but it may not supported by all filesystems or it can be broken (UAE fs implementation CHANGE_FH variant was buggy until about 2 years ago. It is really rarely used in AOS programs). Amiga SAS-C open() seems to use Open(NEWFILE)/Close()/Open(OLDFILE) method to handle O_TRUNC instead of SetFileSize() so this method appears to be safe. Actually flock() calls NameFromFH() which itself calls DupLockFromFH(). So the first question is if NameFromFH() should fail on a handle opened with MODE_NEWFILE ? Does it on OS3.x ? NameFromFH() must work with MODE_NEWFILE opened filehandles. It is also mentioned in Guru Book that says AOS NameFromFH() internally uses ExamineFH() and ParentOfFH() to get the name string because DupLockFromFH() can't work if handle is MODE_NEWFILE.
It looks like it does something like this:
- parentlock = ParentOfFH(fh);
- Path string = NameFromLock(parentlock);
- Unlock(parentlock)
- Append file name to path string from ExamineFH(fh)
What are the technical reasons for not making DupLockFromFH() work on MODE_NEWFILE files? IIRC it's because MODE_NEWFILE uses an exclusive lock (on all filesystems, not just on AmberRAM).
Would there be any issue if we just made it work? UAE's filesystem would have to be reworked, but that's all about it, no? Wouldn't it break compatibility with AOS m68k filesystems? Yes. Exclusive lock duplication (which includes MODE_NEWFILE filehandles that have internal exclusive lock) attempts must fail. It is documented behavior. NameFromFH() is now rewritten and works with exclusive filehandles.
At the moment can get the name of the 'double-clicked' file with some code
if (argc == 0) { struct WBStartup *startup = (struct WBStartup *) argv; if (startup->sm_NumArgs > 1) { BPTR parentlock = startup->sm_ArgList[1].wa_Lock; char *filename = startup->sm_ArgList[1].wa_Name; } }
But cannot get the directory from which my file is 'double-clicked' using "GetCurrentDirName". If set CLI as ToolType, GetCurrentDirName works, but then get bogus data as filename. As your program is launched by Workbench, it doesn't have a CLI structure indeed, hence why GetCurrentDirName() doesn't work without CLI tooltype. But you should be able to use NameFromLock() and find what you need from there.
BPTR GetCurrentDir (void) {
BPTR dir;
dir = CurrentDir(0);
CurrentDir(dir);
return dir;
}
GetCurrentDirName() gets the current path string from the program's CLI structure and returns an error if there is no CLI structure (IoErr() == ERROR_OBJECT_WRONG_TYPE).
#include <proto/exec.h> #include <proto/dos.h> #include <dos/dostags.h> /* BOOL myGetCurrentDirName (STRPTR buffer,LONG length) { BPTR lock; BOOL success = FALSE; if (lock = Lock ("",SHARED_LOCK)) { success = NameFromLock (lock,buffer,length); UnLock (lock); } return (success); } */ __saveds void proc (void) { struct Process *pr = (struct Process *) FindTask (NULL); struct Message *msg; BPTR win; WaitPort (&pr->pr_MsgPort); msg = GetMsg (&pr->pr_MsgPort); win = Open ("con:////DirName/CLOSE/WAIT",MODE_NEWFILE); if (win) { char buffer[256]; if (GetCurrentDirName (buffer,256)) { FPrintf (win,"Dir Name = <%s>n",buffer); } else { Fault (IoErr(),NULL,buffer,256); FPrintf (win,"Error: %sn",buffer); } Close (win); } Forbid(); ReplyMsg (msg); } int main (void) { struct Message *msg = AllocVec (sizeof(struct Message),MEMF_CLEAR|MEMF_PUBLIC); if (msg) { struct MsgPort *port = CreateMsgPort(); if (port) { struct Process *pr = CreateNewProcTags (NP_Entry,proc,TAG_END); if (pr) { msg->mn_ReplyPort = port; PutMsg (&pr->pr_MsgPort,msg); WaitPort (port); GetMsg (port); } DeleteMsgPort (port); } FreeVec (msg); } return (0); }
[edit] Examples
#include <stdio.h> #include <proto/dos.h> #include <dos/dos.h> #include <stdlib.h> int main() { FILE *fd; char buffer[32]; int i; BPTR file; fd = fopen( "seek.txt", "wb" ); if ( !fd ) { fprintf( stderr, "Could not write test file seek.txt\n" ); exit(-1); } fprintf( fd, "() does not work!\n" ); fclose(fd); /* fseek() */ fd = fopen( "seek.txt", "rb" ); if ( !fd ) { fprintf( stderr, "Could not open test file seek.txt\n" ); exit(-1); } i = fread( buffer, 1, 1, fd ); //printf("pos=%ld\n",ftell(fd)); i += fread( &buffer[1], 1, 6, fd ); if( i != 7 ) { fprintf( stderr, "Wanted to fread() %d chars, but could only get %d!\n", 6, i-1 ); exit(-1); } fseek( fd, 4, SEEK_CUR ); i = fread( &buffer[7], 1, 11, fd ); buffer[7+i]=0; printf( "fseek%s", buffer ); fclose(fd); /* Seek() */ file = Open( "seek.txt", MODE_OLDFILE ); i = Read( file, buffer, 7 ); Seek( file, 4, OFFSET_CURRENT ); i += Read( file, &buffer[7], 11 ); buffer[i] = 0; printf( "\nSeek%s", buffer ); return 0; }
#include <stdlib.h> /* Write some numbers to a file */ void WriteNumbers(void) { BPTR fh; int n; UBYTE numstr[5]; printf("Writing numbers to numbers.dat\n\n"); /* Open file for writing */ fh = Open("numbers.dat", MODE_NEWFILE); if (fh) { for (n=1; n<=20; n++) { /* Convert number to a string with new line & write string to file */ sprintf(numstr, "%d\n", n); FPuts(fh, numstr); } Close(fh); } } /* Read numbers from a file */ void ReadNumbers(void) { BPTR fh; int i; UBYTE numstr[5]; UBYTE *buffer; printf("Reading file numbers.dat.\n"); /* Open existing file for reading */ fh = Open("numbers.dat", MODE_OLDFILE); if (fh) { /* Read a string and check for End of File */ while (buffer = FGets(fh, numstr, 5L)) { /* Convert string to number and print it */ i = atoi(numstr); printf("Number %d\n", i); } Close(fh); } printf("EOF found\n"); } int main(void) { WriteNumbers(); ReadNumbers(); return 0; }
#include <stdlib.h> #include <dos/dos.h> #include <defines/dos.h> extern struct SYSBase *SysBase; extern struct DOSBase *DOSBase; int main(int argc, char *argv[]) { int ioerr, fsize, res; BPTR fhandle; if (argc != 2) { printf("Wrong number of arguments\n"); return 1; } fhandle = Open(argv[1], MODE_OLDFILE); if (!fhandle) { ioerr = IoErr(); printf("Cannot open '%s'\n", argv[1]); PrintFault(ioerr, "IoErr() says: "); return 1; } res = Seek(fhandle, 0, OFFSET_END); if (res == -1) { ioerr = IoErr(); printf("Cannot seek to the end of file\n"); PrintFault(ioerr, "IoErr() says: "); Close(fhandle); return 1; } fsize = Seek(fhandle, 0, OFFSET_BEGINNING); printf("File size = %d\n", fsize); Close(fhandle); return 0; }
[edit] Filesystems
A device, handler and filesystem MUST MUST MUST be re-entrant.
The difference between a handler and a filesystem is that a filesystem is required to expose a directory structure and files. Implicitly this means a good 60% of dos packets.
The general rule of thumb was to implement 'virtual' devices (no underlying hardware) as a handler rather than a full blown file system as in the case of ram-handler. Obviously that is not the written in stone but it is the established pattern for the OS.
Emulating a foreign filesystem falls into the class of a full blown fileystem, however given that there is no underlying hardware for a virtual device, the need for a device is probably a waste of effort and better implemented as fat-handler.
After "Assign DISMOUNT"? I don't know, but AROS's Assign command didn't call ACTION_END, which would be necessary to shut down the handler fully?
Filesystems can be locked with LDF_READ|LDF_DEVICES, but unlocked with LDF_READ|LDF_VOLUMES.
A device entry should not be removed from dl_DosList or the code unloaded until all associated locks either direct or indirect have been released. Specifically, if a device has medium mounted (aka volume), any attempt to remove the device, be it a hardware device or a virtual device MUST be denied. The best way to detect if a device is mounted without triggering a requester that says "Please insert volume BLAH:" is to use LockDosList \ AttemptLockDosList and then FindDOSEntry.
struct Process * this_process; APTR old_window_ptr; BPTR lock; this_process = (struct Process *)FindTask(NULL); old_window_ptr = this_process->pr_WindowPtr; this_process->pr_WindowPtr = (APTR)-1; UnLock(lock = Lock("device_name:",SHARED_LOCK)); this_process->pr_WindowPtr = old_window_ptr;
If the lock obtained is not (BPTR)NULL then the device/volume/assignment you tried is available, otherwise it's not. Note that this method does not specifically check for devices, you'd have to scan the DOSList for that.
Given that a dl_Volume and a dl_Device are 'paired', any attempt to remove the device should validate the device's volume and its associated locks and return a fail with the appropriate error code.
fm2000 source.
[edit] Process
Plain tasks have limitations in as much they must not call a function of dos.library or a function that could call a function of dos.library. Processes do not have this limitation.
A process is an expanded task. Opposed to a task, it can use functions of dos.library, because a process structure contains some special fields, concerning files and directories.
struct Process {
struct pr_Task;
struct pr_MsgPort;
WORD pr_pad;
BPTR pr_SegList;
LONG pr_stackSize;
APTR pr_GlobVec;
LONG pr_TaskNum;
BPTR pr_StackBase;
LONG pr_Result2;
BPTR pr_CurrentDir;
BPTR pr_CIS;
BPTR pr_COS;
APTR pr_ConsoleTask;
APTR pr_FileSystemTask;
BPTR pr_CLI;
APTR pr_ReturnAddr;
APTR pr_PktWait;
APTR pr_WindowPtr;
BPTR pr_HomeDir;
LONG pr_Flags;
APTR pr_ExitCode;
LONG pr_ExitData;
APTR pr_Arguments;
struct pr_LocalVrs;
APTR pr_ShellPrivate;
BPTR pr_CES;
};
pr_Flags Flags
prb_FreeSegList prb_FreeCurrDir prb_FreeCli prb_CloseInput prb_CloseOutput prb_FreeArgs
It's cleaner than shoving everything in globals. Also, let's say you wish to run the same thread code on slightly different data (not much point unless you have multiple CPU cores, but anyway), NP_UserData allows you to use the same code over and over, just passing it different start data. You could, of course set up a message port and do it that way, but for simple tasks find passing a direct pointer to the data and using semaphores, easier. You can share just about any library pointer between processes.
Every process using bsdsocket.library must open that library on its own. Sharing bsdsocketbase is not permitted.
Generally speaking tasks should share the same address space, while processes not, and at least for that reason libraries should be reopened personally by each process.
Because people are not respecting such rules it is not possible to introduce memory protection mechanisms to the system.
The easiest way is to call CreateNewProc() with no tags for the NP_Input, NP_Output,NP_Error, these will then open a default "NIL:" stream for the child.
int ProcEntry () { struct Process *proc; struct Message *msg; proc = (struct Process *)FindTask(NULL); WaitPort(&proc->pr_MsgPort); msg = GetMsg(&proc->pr_MsgPort); /* ... */ }
Just make sure you properly (Semaphore) protect any resources such as IO handles etc. that you intend to share between threads and all should be OK.
CreateNewProc() should not create BCPL stack frame and register setup (A5=BCPL_jsr and other related weirdness), only RunCommand() needs to do it. Can see the reason for confusion because CreateNewProc() needs to call BCPL_init() which actually only does part of BCPL setup (stupid segarray and some globvec stuff) but it should not setup BCPL stack frames (which replaces A6 with BCPL_rts when all process entries expect SysBase = crash)
CreateNewProc()->CallEntry() should be as it was:
argPtr = A0, argSize = D0, SysBase = A6, no stack swap
And RunProcess() should be the wild & crazy BCPL stuff.
Why was this change made? Don't see how it helped other architectures, and it certainly did not help m68k. But it should still set pr_ReturnAddr, right? No, because pr_ReturnAddr is only used by exits from RunProcess()/RunCommand().
dos/Exit() does work with normal process started with Create(New)Proc(). It is not BCPL specific. (but perhaps it works differently in "BCPL mode", I haven't tested this yet) AROS/DOS/Exit() should be a no-op. The only callers of Exit() were BCPL routines, and there is a BCPL specific implementation in arch/m68k-amiga/dos/bcpl.S to support those old programs.
Dos/Exit() was a mistake in AmigaOS, and we should not be making it a first class function. If we want to implement a C-style exit, it should be in the AROS C library, implementing memory free and library closing, etc.
Read more here
[edit] Packets
struct DosPacket {
struct Message *dp_Link; /* EXEC message */
struct MsgPort *dp_Port; /* Reply port for the packet */
/* Must be filled in each send. */
LONG dp_Type; /* See ACTION_... 'R' means Read, 'W' means Write to the file system */
LONG dp_Res1; /* result that would have been returned by the function,
e.g. Write ('W') returns actual
* length written */
LONG dp_Res2; /* For file system calls - returned by IoErr() */
/* Device packets common equivalents */
#define dp_Action dp_Type
#define dp_Status dp_Res1
#define dp_Status2 dp_Res2
#define dp_BufAddr dp_Arg1
LONG dp_Arg1;
LONG dp_Arg2;
LONG dp_Arg3;
LONG dp_Arg4;
LONG dp_Arg5;
LONG dp_Arg6;
LONG dp_Arg7;
};
struct StandardPacket {
struct Message sp_Msg;
struct DosPacket sp_Pkt;
}; /* StandardPacket */
/* Packet types */
#define ACTION_NIL 0
#define ACTION_STARTUP 0
#define ACTION_GET_BLOCK 2 /* OBSOLETE */
#define ACTION_SET_MAP 4
#define ACTION_DIE 5
#define ACTION_EVENT 6
#define ACTION_CURRENT_VOLUME 7
#define ACTION_LOCATE_OBJECT 8
#define ACTION_RENAME_DISK 9
#define ACTION_WRITE 'W'
#define ACTION_READ 'R'
#define ACTION_FREE_LOCK 15
#define ACTION_DELETE_OBJECT 16
#define ACTION_RENAME_OBJECT 17
#define ACTION_MORE_CACHE 18
#define ACTION_COPY_DIR 19
#define ACTION_WAIT_CHAR 20
#define ACTION_SET_PROTECT 21
#define ACTION_CREATE_DIR 22
#define ACTION_EXAMINE_OBJECT 23
#define ACTION_EXAMINE_NEXT 24
#define ACTION_DISK_INFO 25
#define ACTION_INFO 26
#define ACTION_FLUSH 27
#define ACTION_SET_COMMENT 28
#define ACTION_PARENT 29
#define ACTION_TIMER 30
#define ACTION_INHIBIT 31
#define ACTION_DISK_TYPE 32
#define ACTION_DISK_CHANGE 33
#define ACTION_SET_DATE 34
#define ACTION_SCREEN_MODE 994
#define ACTION_READ_RETURN 1001
#define ACTION_WRITE_RETURN 1002
#define ACTION_SEEK 1008
#define ACTION_FINDUPDATE 1004
#define ACTION_FINDINPUT 1005
#define ACTION_FINDOUTPUT 1006
#define ACTION_END 1007
#define ACTION_SET_FILE_SIZE 1022 /* fast file system only in 1.3 */
#define ACTION_WRITE_PROTECT 1023 /* fast file system only in 1.3 */
/* new 2.0 packets */
#define ACTION_SAME_LOCK 40
#define ACTION_CHANGE_SIGNAL 995
#define ACTION_FORMAT 1020
#define ACTION_MAKE_LINK 1021
/**/
/**/
#define ACTION_READ_LINK 1024
#define ACTION_FH_FROM_LOCK 1026
#define ACTION_IS_FILESYSTEM 1027
#define ACTION_CHANGE_MODE 1028
/**/
#define ACTION_COPY_DIR_FH 1030
#define ACTION_PARENT_FH 1031
#define ACTION_EXAMINE_ALL 1033
#define ACTION_EXAMINE_FH 1034
#define ACTION_LOCK_RECORD 2008
#define ACTION_FREE_RECORD 2009
#define ACTION_ADD_NOTIFY 4097
#define ACTION_REMOVE_NOTIFY 4098
#define ACTION_SERIALIZE_DISK 4200
struct ErrorString {
LONG *estr_Nums;
UBYTE *estr_Strings;
};
AFS handler for example isn't fully "dos packetized" yet. ("real" dos packets and FSA stuff can't work together) They can. Long time ago we discussed this with Rob Norris. The basic idea was to check what is pointed to by file_handle or lock's Port (or Process) member. It's either NT_PROCESS, or NT_DEVICE type node. This way you can distinguish what are you talking to. Additionally, your con.handler should be able to work nicely with packet.handler. If it doesn't, packet.handler needs to be fixed.
[edit] Reference
Some issues, all related to AROS's hardcoded 32bit values etc (fib_Size,for starters). What is the best thing to do about this? MorphOS uses 64bit versions of the DOS functions and structures, though im not sure about OS4
Only know that OS4 uses following 64-bit packet types because I was asked to support them in WinUAE directory filesystem emulation few years ago:
#define ACTION_CHANGE_FILE_POSITION64 8001 #define ACTION_GET_FILE_POSITION64 8002 #define ACTION_CHANGE_FILE_SIZE64 8003 #define ACTION_GET_FILE_SIZE64 8004
Old packets return file size = 2GB-1 if real size is >=2G. guess we need to alter DoPkt etc to return a QUAD? OK from what I can see DoPkt and the internal dopacket both need changed to return a QUAD, but also DosPacket->dp_Res1 needs to be a QUAD (which will break compatability afaict). I think this is more desirable than trying to change all of the dos structures to use 64bit values though?
Is it enough to say code accessing blocknr/size/etc should check if they equal 0x7FFFFFFF, and use 64bit packet calls directly if so?
MorphOS, phase5, Ralph Babel and some CBM engineers defined [http://babel.de/download/trackdisk64.tar.bz2
TD64 standard]. MorphOS dosextens.h that describes their 64-bit packets extensions.
TD64 has nothing to do with 64bit DOS file access. And yes, we support TD64 (along with NSD) for very long time already. Without it, AROS couldn't access more than first 4GB of any hard drive.
[edit] Simple Input/Output and File Operations
APTR Open( const char *filename, ULONG mode ); Open filename for reading (MODE_OLDFILE), for writing (MODE_NEWFILE) or for both read and write (MODE_READWRITE). Returns NULL if the file can't be opened in the desired mode.
MODE_NEWFILE will create a new file and delete the old version if any exists. MODE_READWRITE will open an existing file for writing. Open in this mode will fail if the file does not already exist or the file is already opened by anyone else.
LONG Close( APTR handle ); Closes a file handle previously opened by Open(). May return DOS_FALSE if the close fails. Normally this can only happen on storage media error.
LONG Read( APTR handle, char *buffer, LONG size ); Reads data from a handle and places it into the buffer. size is the number of characters to read. Read() returns the number of characters actually read, 0 for an EOF condition and negative values if there was a fatal error (such as protection violation).
Interactive handlers are allowed to return less data than requested. This does not mean that there is no more data to be read ! In raw mode Read() does not block if WaitForChar() returned DOS_TRUE.
LONG Write( APTR handle, const char *buffer, LONG size ); Writes size characters from the buffer to the file handle. Returns the number of characters actually written or 0 for an error.
LONG Seek( APTR handle, LONG offset, LONG direction ); Changes the current reading/writing position in the file. direction specifies the way offset is interpreted. For OFFSET_CURRENT it is relative to the current position, with OFFSET_BEGINNING it is relative to the beginning of the file and with OFFSET_END relative to the end of the file.
If you try to seek out of the bounds of the file, DOS_FALSE (0) is returned. If the seek was successful, DOS_TRUE (non-0) is returned and the old position is returned as a secondary error, which can be read with IoErr().
LONG IoErr( void ); Returns the secondary result code from the last dos.library call. Normally the code returned identifies why the previous operation failed (if it did fail). However, the secondary result can also be used to return useful information, such as the number of characters waiting (WaitForChar()) or the old seek position (Seek()).
void SetIoErr( LONG code ); Sets the secondary result code of the current process.
LONG PrintFault( LONG code, const char *header ); Displays a human-readable version of the secondary result code. header is a string that is printed after the error message and is usually the filename used in the previously failed call.
[edit] Special Streams
APTR Input( void ); Returns the current standard input stream handle.
APTR Output( void ); Returns the current standard output stream handle.
APTR Error( void ); Returns the current standard error stream handle.
LONG SetMode( APTR handle, LONG mode ); Changes the operating mode of the handle. Different handlers may have different ideas about how to interpret mode, but all handlers should use 0 (MODE_CONSOLE) for console editing mode and -1 for raw mode.
The serial handler translates single newlines and carriage returns to CR+LF pairs, even in raw mode. To bypass this, use SetMode(handle, MODE_RAW | MODE_NOCRLF);.
LONG IsInteractive( APTR handle ); Returns DOS_TRUE for interactive streams.
LONG WaitForChar( APTR handle, ULONG time ); Waits maximum of time ticks for a character to arrive. Works only with interactive streams (in raw mode). If characters are already waiting, returns immediately. Returns DOS_FALSE if the timeout is reached.
After a successful WaitForChar() IoErr() can be used to find out about how many characters (at least) are waiting to be read. The next Read() will NOT block if there are any characters waiting and the stream is in raw mode.
[edit] File Manipulation Functions
LONG DeleteFile( const char *file ); Deletes a file or a directory. If there are locks held to the file or directory, or the directory is not empty, the delete fails (DOS_FALSE is returned).
LONG Rename( const char *oldName, const char *newName ); Changes a file/directory's name from oldName to newName. If newName exists, but it can't be deleted Rename() fails. Full paths can be used, but renaming across different handlers may not be supported. However, moving files inside the same filesystem is possible.
APTR SetComment( const char *name, const char *comment ); Attaches a comment string to a file or directory. comment may be up to 79 characters long. Longer comment strings will be silently truncated.
APTR SetProtection( const char *name, ULONG protection ); Sets the protection flags for a file or directory. Can only be used by the owner of the directory entry.
ULONG SetOwner( const char *name, ULONG ownerinfo ); Sets the owner ID and group ID for a file. User ID is the top 16 bits in ownerinfo, while group ID uses the lower 16 bits. Can only be used by the owner of the directory entry.
LONG SetFileDate( const char *name, const struct DateStamp *date ); Sets the creation date of a file. Can only be used by the owner of the directory entry.
[edit] File Locking and Directory Functions
APTR Lock( const char *name, LONG mode ); Creates a shared (ACCESS_READ) or an exclusive lock (ACCESS_WRITE) on a file or a directory. Only one exclusive lock can be held on an object simultaneously. If the object can't be locked, NULL is returned.
LONG UnLock( APTR lock ); Releases a previously created lock.
APTR DupLock( APTR oldLock ); Makes a duplicate of a lock.
LONG SameLock( APTR lockA, APTR lockB ); Returns DOS_TRUE if the locks are for the same object, DOS_FALSE otherwise.
LONG Examine( APTR lock, struct FileInfoBlock *fib ); Fills in the structure with information about the locked object. Returns DOS_FALSE for failure.
LONG ExNext( APTR lock, struct FileInfoBlock *fib ); Fills in the structure with information about the next object in the directory. Returns DOS_FALSE for error. If IoErr() reports ERROR_NO_MORE_ENTRIES, the previous directory entry was the last one.
Examine() should be always called with the lock before using ExNext() or the results may be uncorrect. If the original lock was for a file, ExNext() always fails.
LONG ExamineEnd( APTR lock, struct FileInfoBlock *fib ); Ends the directory scanning.
APTR CreateDir( const char *name ); Creates a directory and returns an exclusive lock to it. If the directory can't be created, NULL is returned.
APTR CurrentDir( APTR newCurrentDir ); Changes the current working directory to the directory referenced by the lock newCurrentDir.
Returns NULL if the lock is not suitable or for example the directory does not have execute permissions for the user. If successful, returns the lock to the old current directory. This lock should be UnLock():ed by the user.
APTR ParentDir( APTR lock ); Returns a lock to the parent directory of an object lock. If the lock is for a root directory of a handler, NULL is returned.
[edit] Disk Functions
LONG Format( const char *disk, ULONG flags ); Formats a disk. The disk must be 'owned' by the user.
LONG DiskInfo( APTR lock, struct InfoData *info ); Fills in the structure with information about the disk the object referenced by the lock resides in.
LONG Relabel( const char *handler, const char *newname ); Gives a new name to a disk.
LONG IsFileSystem( const char *handlername ); Determines if a handler is a filesystem or a special handler. Program Loading and Running
struct SegList *ROMLoadSeg( const char *name ); Loads a relocatable program named name from the internal list of commands, initializes and relocates the code and data segments and reserves a BSS section. If name is not found or there is an error (memory allocation or relocation failed), NULL is returned.
struct SegList *LoadSeg( const char *name ); Loads a relocatable program from a file name, initializes and relocates the code and data segments and reserves a BSS section. If name is not found or there is an error (memory allocation or relocation failed), NULL is returned.
LONG UnLoadSeg( struct SegList *segList ); Unloads code, data and BSS created by ROMLoadSeg() or LoadSeg(). It is safe to call UnLoadSeg() with a NULL segList.
LONG RunCommand( struct SegList *segList, ULONG stacksize, const char *args, ULONG arglen ); Synchronously runs a program previously loaded by ROMLoadSeg() or LoadSeg().
struct Process *CreateProc( const char *name, LONG pri, struct SegList *segList, ULONG stackSize, const char *argString ); Creates a new process and run a program previously loaded by ROMLoadSeg() or LoadSeg().
struct Process *CreateNewProcTags( ULONG tag1type, ... ); Creates a new process and run a program previously loaded by ROMLoadSeg() or LoadSeg().
Many kinds of parameters can be set if the default values do not fit the purpose:
| NP_Seglist | seglist of code to run |
| NP_FreeSeglist | free seglist on exit. Default is TRUE |
| NP_Entry | entry point (mutually exclusive with NP_SegList) |
| NP_Input | fh - default is Open("NIL:", MODE_OLDFILE); |
| NP_Output | fh - default is Open("NIL:", MODE_NEWFILE); |
| NP_Error | fh - default is Open("NIL:", MODE_NEWFILE); |
| NP_CloseInput | close input filehandle on exit - default is TRUE |
| NP_CloseOutput | close output filehandle on exit - default is TRUE |
| NP_CloseError | close error filehandle on exit- default is TRUE |
| NP_CurrentDir | lock - default is parent's dir |
| NP_StackSize | stacksize for process - default is 4000 |
| NP_Name | name for process - default is "New Process" |
| NP_Priority | priority - default same as parent |
| NP_CopyVars | to copy local vars - default is TRUE |
| NP_Path | path - default is copy of parent's path |
| NP_Arguments | argument string (char *) |
| TAG_DONE | ends the tags |
| NP_ConsoleTask | |
| NP_WindowPtr | |
| NP_HomeDir | |
| NP_Cli | |
| NP_CommandName | |
| NP_NotifyOnDeath | |
| NP_Synchronous | |
| NP_ExitCode | |
| NP_ExitData | |
| NP_UserData |
Example:
CreateNewProcTags( NP_Seglist, segl,
NP_Name, "MyChild",
NP_CopyVars, FALSE,
TAG_DONE );
LONG SystemTags( const char *command, ULONG tag1type, ... );
Currently always fails (not implemented).
Local Variables
LONG DeleteVar( const char *name, ULONG flags );
Remove a variable.
struct LocalVar *FindVar( const char *name, ULONG flags );
[edit] Find a variable
LONG GetVar( const char *name, char *buffer, LONG bufsize, ULONG flags ); Get the contents of a variable name. If the variable does not exist, -1 is returned, otherwise the size of the value is returned. bufsize+1 is returned if the value does not fit into the buffer. When I open the library manually, all works fine (the GetVar() is in the LibOpen() code). When I use mount, it crashes solidly. You are not allowed to Wait() in library init code, so most DOS functions are off limits. There's also the possibility of deadlocks involving access to the DOS list etc. Maybe you need to delegate reading the variables to a task owned by your driver so that it can be done after the library/driver has opened. I moved the GetVar() call to the beginning of my spawned task - same thing happens. The task hangs. The computer doesn't, but the task never gets any further... Is it legal to call GetVar() from a task? (as opposed to a DOS process). Aren't most dos.library functions off limits in tasks too? If GetVar is off limits in the library init code it very likely is off limits in a normal task as well. Try creating a process instead. Really there's few places where it's worth creating a task instead of a process. According to autodocs CreateNewProc() is callable from a task, though behavior will be somewhat different. No idea if it's also safe from library init code. For GVF_GLOBAL_ONLY, you'll definitely need to be in a Process, as dos.library will call Open()/Read()/Close() on the file in ENV: I wonder if this may be working in AmigaOS by having GetVar() return 0 if called from a Task context. Is it actually reading the "catweasel.prefs" in AmigaOS? If it is, then it sounds like there is some sort of behavior where dos.library is looking for a parent process of your Task. If it is, then I'd say that would be a worthwhile behavior to emulate, especially since the AROS C library seems to do much the same thing to get the arosc_userdata structure.
LONG SetVar( const char *name, const char *buffer, LONG size, ULONG flags ); Create or change a variable name contents. If you are creating string variables (as opposed to binary), size should be strlen(buffer)+1. flags is used to differentiate local variables (0) and aliases (VAR_ALIAS).
[edit] DOS Packets
struct DevProc *GetDeviceProc( const char *handler, struct DevProc *old ); Get an instance of a handlers messageport and lock of the handlers root directory (if a filesystem). Pass NULL for old, or the structure previously returned by GetDeviceProc() to get the next handler/lock combination in a multiassign.
void FreeDeviceProc( struct DevProc *dev ); Free the structure allocated by GetDeviceProc().
struct MsgPort *DeviceProc( const char *handler ); Obsolete: DO NOT USE! Use GetDeviceProc() and FreeDeviceProc() instead.
LONG DoPkt4( struct MsgPort *port, LONG type, ULONG arg1, ULONG arg2, ULONG arg3, ULONG arg4 ); Sends a DOSPacket to a message port. All I/O operations are performed by sending and receiving DOSPackets (DoPkt4() is called internally). type is the action to be performed, arg1..4 are the arguments.
LONG DoPkt4Safe( const char *handler, LONG type, ULONG arg1, ULONG arg2, ULONG arg3, ULONG arg4 ); Sends a DOSPacket to a named handler.
[edit] Assigns
LONG AssignLock( const char *assign, APTR lock ); Creates an assign (logical device) with the name assign and makes it point to the supplied lock. Any previous assign with the same name is first discarded.
If the call succeeds, the lock becomes the property of the dos.library and may not be freed or otherwise used by the program.
LONG AssignLate( const char *assign, const char *path ); Creates a late-binding assign. The supplied path is resolved only when the assign is first accessed. If path does not point to a valid object, access through the assign fails and resolving is again attempted when the assign is next accessed. Any previous assign with the same name is discarded.
LONG AssignAdd( const char *assign, APTR lock ); Adds objects to an already existing assign.
If the call succeeds, the lock becomes the property of the dos.library and may not be freed or otherwise used by the program. Also, assigns can be created to point to files also, but it may be quite confusing to see them used that way.
[edit] Output Formatting
LONG PrintFmt( va_list ap, void *fp, int (*cf)(int, void *), const char *fmt ); Format a string using a callback function (cf). fp is passed to the callback.
LONG FPrintF( APTR handle, const char *fmt, ... ); Prints formatted strings to a filehandle. Currently this is VERY slow, because each character in the format string is displayed separately and each Write() causes at least two context switches.
Until buffering is added you should only use format commands in the format string to get the best performance. FPrintF(Output(), "%s%d\n", "Hello ", count);
LONG PrintF( const char *fmt, ... ); Prints formatted strings to the standard output stream. Currently VERY slow.
[edit] Miscellaneous
struct ArgList *ParseArgs( const char *line ); Parses an argument string into the ANSI-C format, where each argument is in its own string.
void FreeArgs( struct ArgList *args ); Frees the structure created by ParseArgs.
void Delay( ULONG ticks ); Makes the process wait for at least ticks-1 number of 1/100 seconds.
the four public structures are involved, DosLibrary (dl_), RootNode (rn_), DosInfo (di_), and DevInfo (dvi_).
DosLibrary structure starts with an exec based Library structure and adds extras after it.
struct DosLibrary {
struct Library dl_lib;
APTR dl_Root;
APTR dl_GV;
LONG dl_A2;
LONG dl_A5;
LONG dl_A6;
};
[edit] DosBase
revisited DOSBase and removed almost all AROS-specific fields from it:
dl_DevInfo - replaced with di_DevInfo in struct DosInfo
dl_SegList - simply not used, dos.library is ROM resident and doesn't have own seglist.
dl_ResList - removed, now it's DosInfo->di_ResList
The only thing that is left over is dl_Flags. DirectoryOpus uses this on AROS. Can it be replaced correct rn_Flags right now, or this should be done in ABI v1. Other private fields are still there, they are really private.
struct RootNode {
BPTR rn_TaskArray;
BPTR rn_ConsoleSegment;
struct DateStamp rn_time;
LONG rn_RestartSeg;
BPTR rn_Info;
BPTR rn_FileHandlerSegment;
};
struct DosInfo {
BPTR di_McName;
BPTR di_DevInfo;
BPTR di_Devices;
BPTR di_Handlers;
BPTR di_NextEntry;
LONG di_UseCount;
BPTR di_SegPtr;
BPTR di_SegName;
};
struct DevInfo {
BPTR dvi_Next;
LONG dvi_Type;
APTR dvi_Task;
BPTR dvi_Lock;
BPTR dvi_Handler;
LONG dvi_StackSize;
LONG dvi_Priority;
LONG dvi_Startup;
BPTR dvi_SegList;
BPTR dvi_GlobVec;
BPTR dvi_Name;
};
After the FGetC-s come Seek-s and then a Read. all for that same file_handle. I haven't checked what the arguments are and what happens exactly, but that's similar to the pattern for precise memory allocation: With 'getc' you read the file buffer in steps of one and only count the characters or bytes, until you get to the end of the word or line or file or whatever you want to read. Then you 'seek' to back up to where you started, you 'alloc' the counted size of memory, and then you 'read' the whole thing in one go. It's a way to avoid memory related problems, but it may be rather heavy on the i/o-side.
FGetC like C (f)getc is a function meant to be called a lot of time. It is often used as a function pointer passed to parsing functions. Programs using fscanf etc. will use a lot of FGetC. As it is buffered I doubt it is a performance bottleneck (unless you patch it of course and print out a line on a console with each call).
#include <proto/dos.h> #include <proto/utility.h> UBYTE get_char() { UBYTE buffer; BPTR in = Input(); SetMode (in,1); /* set to RAW mode */ Read (in,&buffer,1); SetMode (in,0); /* set to CON mode */ if (buffer == '\r') buffer = '\n'; return (ToUpper (buffer)); } int main (void) { UBYTE key; Printf ("Please press a key: "); Flush (Output()); key = get_char(); Printf ("\nThe key you pressed is %lc\n",key); return (0); }
This page may need to be