1 /*
2   Hatari - gemdos.c
3 
4   This file is distributed under the GNU General Public License, version 2
5   or at your option any later version. Read the file gpl.txt for details.
6 
7   GEMDOS intercept routines.
8   These are used mainly for hard drive redirection of high level file routines.
9 
10   Host file names are handled case insensitively, so files on GEMDOS
11   drive emulation directories may be either in lower or upper case.
12 
13   Too long file and directory names and names with invalid characters
14   are converted to TOS compatible 8+3 names, but matching them back to
15   host names is slower and may match several such filenames (of which
16   first one will be returned), so using them should be avoided.
17 
18   Bugs/things to fix:
19   * Host filenames are in many places limited to 255 chars (same as
20     on TOS), FILENAME_MAX should be used if that's a problem.
21   * rmdir routine, can't remove dir with files in it. (another tos/unix difference)
22   * Fix bugs, there are probably a few lurking around in here..
23 */
24 const char Gemdos_fileid[] = "Hatari gemdos.c : " __DATE__ " " __TIME__;
25 
26 #include <config.h>
27 
28 #include <sys/stat.h>
29 #if HAVE_STATVFS
30 #include <sys/statvfs.h>
31 #endif
32 #include <sys/types.h>
33 #if HAVE_UTIME_H
34 #include <utime.h>
35 #elif HAVE_SYS_UTIME_H
36 #include <sys/utime.h>
37 #endif
38 #include <time.h>
39 #include <ctype.h>
40 #include <unistd.h>
41 #include <errno.h>
42 
43 #include "main.h"
44 #include "cart.h"
45 #include "configuration.h"
46 #include "file.h"
47 #include "floppy.h"
48 #include "ide.h"
49 #include "inffile.h"
50 #include "hdc.h"
51 #include "gemdos.h"
52 #include "gemdos_defines.h"
53 #include "log.h"
54 #include "m68000.h"
55 #include "memorySnapShot.h"
56 #include "printer.h"
57 #include "statusbar.h"
58 #include "scandir.h"
59 #include "stMemory.h"
60 #include "str.h"
61 #include "tos.h"
62 #include "hatari-glue.h"
63 #include "maccess.h"
64 #include "symbols.h"
65 
66 /* Maximum supported length of a GEMDOS path: */
67 #define MAX_GEMDOS_PATH 256
68 
69 #define BASEPAGE_SIZE (0x80+0x80)  /* info + command line */
70 #define BASEPAGE_OFFSET_DTA 0x20
71 #define BASEPAGE_OFFSET_PARENT 0x24
72 
73 /* Have we re-directed GemDOS vector to our own routines yet? */
74 bool bInitGemDOS;
75 
76 /* structure with all the drive-specific data for our emulated drives,
77  * used by GEMDOS_EMU_ON macro
78  */
79 EMULATEDDRIVE **emudrives = NULL;
80 
81 #define  ISHARDDRIVE(Drive)  (Drive!=-1)
82 
83 /*
84   Disk Transfer Address (DTA)
85 */
86 #define TOS_NAMELEN  14
87 
88 typedef struct {
89   /* GEMDOS internals */
90   Uint8 index[2];
91   Uint8 magic[4];
92   char dta_pat[TOS_NAMELEN]; /* unused */
93   char dta_sattrib;          /* unused */
94   /* TOS API */
95   char dta_attrib;
96   Uint8 dta_time[2];
97   Uint8 dta_date[2];
98   Uint8 dta_size[4];
99   char dta_name[TOS_NAMELEN];
100 } DTA;
101 
102 #define DTA_MAGIC_NUMBER  0x12983476
103 #define MAX_DTAS_FILES    256      /* Must be ^2 */
104 #define MAX_DTAS_MASK     (MAX_DTAS_FILES-1)
105 #define CALL_PEXEC_ROUTINE 3       /* Call our cartridge pexec routine */
106 
107 #define  BASE_FILEHANDLE     64    /* Our emulation handles - MUST not be valid TOS ones, but MUST be <256 */
108 #define  MAX_FILE_HANDLES    32    /* We can allow 32 files open at once */
109 
110 /*
111    DateTime structure used by TOS call $57 f_dtatime
112    Changed to fix potential problem with alignment.
113 */
114 typedef struct {
115   Uint16 timeword;
116   Uint16 dateword;
117 } DATETIME;
118 
119 #define UNFORCED_HANDLE -1
120 static struct {
121 	int Handle;
122 	Uint32 Basepage;
123 } ForcedHandles[5]; /* (standard) handles aliased to emulated handles */
124 
125 typedef struct
126 {
127 	bool bUsed;
128 	char szMode[4];     /* enough for all used fopen() modes: rb/rb+/wb+ */
129 	Uint32 Basepage;
130 	FILE *FileHandle;
131 	/* TODO: host path might not fit into this */
132 	char szActualName[MAX_GEMDOS_PATH];        /* used by F_DATIME (0x57) */
133 } FILE_HANDLE;
134 
135 typedef struct
136 {
137 	bool bUsed;
138 	int  nentries;                      /* number of entries in fs directory */
139 	int  centry;                        /* current entry # */
140 	struct dirent **found;              /* legal files */
141 	char path[MAX_GEMDOS_PATH];                /* sfirst path */
142 } INTERNAL_DTA;
143 
144 static FILE_HANDLE  FileHandles[MAX_FILE_HANDLES];
145 static INTERNAL_DTA InternalDTAs[MAX_DTAS_FILES];
146 static int DTAIndex;        /* Circular index into above */
147 static Uint16 CurrentDrive; /* Current drive (0=A,1=B,2=C etc...) */
148 static Uint32 act_pd;       /* Used to get a pointer to the current basepage */
149 static Uint16 nAttrSFirst;  /* File attribute for SFirst/Snext */
150 static Uint32 CallingPC;    /* Program counter from caller */
151 
152 /* last program opened by GEMDOS emulation */
153 static bool PexecCalled;
154 
155 #if defined(WIN32) && !defined(mkdir)
156 #define mkdir(name,mode) mkdir(name)
157 #endif  /* WIN32 */
158 
159 #ifndef S_IRGRP
160 #define S_IRGRP 0
161 #define S_IROTH 0
162 #endif
163 
164 /* set to 1 if you want to see debug output from pattern matching */
165 #define DEBUG_PATTERN_MATCH 0
166 
167 
168 /*-------------------------------------------------------*/
169 /**
170  * Routine to convert time and date to GEMDOS format.
171  * Originally from the STonX emulator. (cheers!)
172  */
GemDOS_DateTime2Tos(time_t t,DATETIME * DateTime,const char * fname)173 static void GemDOS_DateTime2Tos(time_t t, DATETIME *DateTime, const char *fname)
174 {
175 	struct tm *x;
176 
177 	/* localtime takes DST into account */
178 	x = localtime(&t);
179 
180 	if (x == NULL)
181 	{
182 		Log_Printf(LOG_WARN, "'%s' timestamp is invalid for (Windows?) localtime(), defaulting to TOS epoch!",  fname);
183 		DateTime->dateword = 1|(1<<5);	/* 1980-01-01 */
184 		DateTime->timeword = 0;
185 		return;
186 	}
187 	/* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */
188 	DateTime->timeword = (x->tm_sec>>1)|(x->tm_min<<5)|(x->tm_hour<<11);
189 
190 	/* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */
191 	DateTime->dateword = x->tm_mday | ((x->tm_mon+1)<<5)
192 		| (((x->tm_year-80 > 0) ? x->tm_year-80 : 0) << 9);
193 }
194 
195 /*-----------------------------------------------------------------------*/
196 /**
197  * Populate a DATETIME structure with file info.  Handle needs to be
198  * validated before calling.  Return true on success.
199  */
GemDOS_GetFileInformation(int Handle,DATETIME * DateTime)200 static bool GemDOS_GetFileInformation(int Handle, DATETIME *DateTime)
201 {
202 	const char *fname = FileHandles[Handle].szActualName;
203 	struct stat fstat;
204 
205 	if (stat(fname, &fstat) == 0)
206 	{
207 		GemDOS_DateTime2Tos(fstat.st_mtime, DateTime, fname);
208 		return true;
209 	}
210 	return false;
211 }
212 
213 /*-----------------------------------------------------------------------*/
214 /**
215  * Set given file date/time from given DATETIME.  Handle needs to be
216  * validated before calling.  Return true on success.
217  */
GemDOS_SetFileInformation(int Handle,DATETIME * DateTime)218 static bool GemDOS_SetFileInformation(int Handle, DATETIME *DateTime)
219 {
220 	const char *filename;
221 	struct utimbuf timebuf;
222 	struct stat filestat;
223 	struct tm timespec;
224 
225 	/* make sure Hatari itself doesn't need to write/modify
226 	 * the file after it's modification time is changed.
227 	 */
228 	fflush(FileHandles[Handle].FileHandle);
229 
230 	/* use host modification times instead of Atari ones? */
231 	if (ConfigureParams.HardDisk.bGemdosHostTime)
232 		return true;
233 
234 	filename = FileHandles[Handle].szActualName;
235 
236 	/* Bits: 0-4 = secs/2, 5-10 = mins, 11-15 = hours (24-hour format) */
237 	timespec.tm_sec  = (DateTime->timeword & 0x1F) << 1;
238 	timespec.tm_min  = (DateTime->timeword & 0x7E0) >> 5;
239 	timespec.tm_hour = (DateTime->timeword & 0xF800) >> 11;
240 	/* Bits: 0-4 = day (1-31), 5-8 = month (1-12), 9-15 = years (since 1980) */
241 	timespec.tm_mday = (DateTime->dateword & 0x1F);
242 	timespec.tm_mon  = ((DateTime->dateword & 0x1E0) >> 5) - 1;
243 	timespec.tm_year = ((DateTime->dateword & 0xFE00) >> 9) + 80;
244 	/* check whether DST should be taken into account */
245 	timespec.tm_isdst = -1;
246 
247 	/* set new modification time */
248 	timebuf.modtime = mktime(&timespec);
249 
250 	/* but keep previous access time */
251 	if (stat(filename, &filestat) != 0)
252 		return false;
253 	timebuf.actime = filestat.st_atime;
254 
255 	if (utime(filename, &timebuf) != 0)
256 		return false;
257 	// fprintf(stderr, "set date '%s' for %s\n", asctime(&timespec), name);
258 	return true;
259 }
260 
261 
262 /*-----------------------------------------------------------------------*/
263 /**
264  * Convert from FindFirstFile/FindNextFile attribute to GemDOS format
265  */
GemDOS_ConvertAttribute(mode_t mode)266 static Uint8 GemDOS_ConvertAttribute(mode_t mode)
267 {
268 	Uint8 Attrib = 0;
269 
270 	/* Directory attribute */
271 	if (S_ISDIR(mode))
272 		Attrib |= GEMDOS_FILE_ATTRIB_SUBDIRECTORY;
273 
274 	/* Read-only attribute */
275 	if (!(mode & S_IWUSR))
276 		Attrib |= GEMDOS_FILE_ATTRIB_READONLY;
277 
278 	/* TODO, Other attributes:
279 	 * - GEMDOS_FILE_ATTRIB_HIDDEN (file not visible on desktop/fsel)
280 	 * - GEMDOS_FILE_ATTRIB_ARCHIVE (file written after being backed up)
281 	 * ?
282 	 */
283 	return Attrib;
284 }
285 
286 
287 /*-----------------------------------------------------------------------*/
288 /**
289  * Populate the DTA buffer with file info.
290  * @return   0 if entry is ok, 1 if entry should be skipped, < 0 for errors.
291  */
PopulateDTA(char * path,struct dirent * file,DTA * pDTA,Uint32 DTA_Gemdos)292 static int PopulateDTA(char *path, struct dirent *file, DTA *pDTA, Uint32 DTA_Gemdos)
293 {
294 	/* TODO: host file path can be longer than MAX_GEMDOS_PATH */
295 	char tempstr[MAX_GEMDOS_PATH];
296 	struct stat filestat;
297 	DATETIME DateTime;
298 	int nFileAttr, nAttrMask;
299 
300 	if (snprintf(tempstr, sizeof(tempstr), "%s%c%s",
301 	             path, PATHSEP, file->d_name) >= (int)sizeof(tempstr))
302 	{
303 		Log_Printf(LOG_ERROR, "PopulateDTA: path is too long.\n");
304 		return -1;
305 	}
306 
307 	if (stat(tempstr, &filestat) != 0)
308 	{
309 		perror(tempstr);
310 		return -1;   /* return on error */
311 	}
312 
313 	if (!pDTA)
314 		return -2;   /* no DTA pointer set */
315 
316 	/* Check file attributes (check is done according to the Profibuch) */
317 	nFileAttr = GemDOS_ConvertAttribute(filestat.st_mode);
318 	nAttrMask = nAttrSFirst|GEMDOS_FILE_ATTRIB_WRITECLOSE|GEMDOS_FILE_ATTRIB_READONLY;
319 	if (nFileAttr != 0 && !(nAttrMask & nFileAttr))
320 		return 1;
321 
322 	GemDOS_DateTime2Tos(filestat.st_mtime, &DateTime, tempstr);
323 
324 	/* Atari memory modified directly through pDTA members -> flush the data cache */
325 	M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA));
326 
327 	/* convert to atari-style uppercase */
328 	Str_Filename2TOSname(file->d_name, pDTA->dta_name);
329 #if DEBUG_PATTERN_MATCH
330 	fprintf(stderr, "DEBUG: GEMDOS: host: %s -> GEMDOS: %s\n",
331 		file->d_name, pDTA->dta_name);
332 #endif
333 	do_put_mem_long(pDTA->dta_size, filestat.st_size);
334 	do_put_mem_word(pDTA->dta_time, DateTime.timeword);
335 	do_put_mem_word(pDTA->dta_date, DateTime.dateword);
336 	pDTA->dta_attrib = nFileAttr;
337 
338 	return 0;
339 }
340 
341 
342 /*-----------------------------------------------------------------------*/
343 /**
344  * Clear given DTA cache structure.
345  */
ClearInternalDTA(int idx)346 static void ClearInternalDTA(int idx)
347 {
348 	int i;
349 
350 	/* clear the old DTA structure */
351 	if (InternalDTAs[idx].found != NULL)
352 	{
353 		for (i = 0; i < InternalDTAs[idx].nentries; i++)
354 			free(InternalDTAs[idx].found[i]);
355 		free(InternalDTAs[idx].found);
356 		InternalDTAs[idx].found = NULL;
357 	}
358 	InternalDTAs[idx].nentries = 0;
359 	InternalDTAs[idx].bUsed = false;
360 }
361 
362 /*-----------------------------------------------------------------------*/
363 /**
364  * Clear all DTA cache structures.
365  */
GemDOS_ClearAllInternalDTAs(void)366 static void GemDOS_ClearAllInternalDTAs(void)
367 {
368 	int i;
369 	for(i = 0; i < ARRAY_SIZE(InternalDTAs); i++)
370 	{
371 		ClearInternalDTA(i);
372 	}
373 	DTAIndex = 0;
374 }
375 
376 
377 /*-----------------------------------------------------------------------*/
378 /**
379  * Match a TOS file name to a dir mask.
380  */
fsfirst_match(const char * pat,const char * name)381 static bool fsfirst_match(const char *pat, const char *name)
382 {
383 	const char *dot, *p=pat, *n=name;
384 
385 	if (name[0] == '.')
386 		return false;           /* skip .* files */
387 
388 	dot = strrchr(name, '.');	/* '*' matches everything except last dot in name */
389 	if (dot && p[0] == '*' && p[1] == 0)
390 		return false;		/* plain '*' must not match anything with extension */
391 
392 	while (*n)
393 	{
394 		if (*p=='*')
395 		{
396 			while (*n && n != dot)
397 				n++;
398 			p++;
399 		}
400 		else if (*p=='?' && *n)
401 		{
402 			n++;
403 			p++;
404 		}
405 		else if (toupper((unsigned char)*p++) != toupper((unsigned char)*n++))
406 			return false;
407 	}
408 
409 	/* printf("'%s': '%s' -> '%s' : '%s' -> %d\n", name, pat, n, p); */
410 
411 	/* The traversed name matches the pattern, if pattern also
412 	 * ends here, or with '*'. '*' for extension matches also
413 	 * filenames without extension, so pattern ending with
414 	 * '.*' will also be a match.
415 	 */
416 	return (
417 		(p[0] == 0) ||
418 		(p[0] == '*' && p[1] == 0) ||
419 		(p[0] == '.' && p[1] == '*' && p[2] == 0)
420 	       );
421 }
422 
423 
424 /*-----------------------------------------------------------------------*/
425 /**
426  * Parse directory from sfirst mask
427  * - e.g.: input:  "hdemudir/auto/mask*.*" outputs: "hdemudir/auto"
428  */
fsfirst_dirname(const char * string,char * newstr)429 static void fsfirst_dirname(const char *string, char *newstr)
430 {
431 	int i=0;
432 
433 	strcpy(newstr, string);
434 
435 	/* convert to front slashes and go to end of string. */
436 	while (newstr[i] != '\0')
437 	{
438 		if (newstr[i] == '\\')
439 			newstr[i] = PATHSEP;
440 		i++;
441 	}
442 	/* find last slash and terminate string */
443 	while (i && newstr[i] != PATHSEP)
444 		i--;
445 	newstr[i] = '\0';
446 }
447 
448 
449 /*-----------------------------------------------------------------------*/
450 /**
451  * Return directory mask part from the given string
452  */
fsfirst_dirmask(const char * string)453 static const char* fsfirst_dirmask(const char *string)
454 {
455 	const char *lastsep;
456 
457 	lastsep = strrchr(string, PATHSEP);
458 	if (lastsep)
459 		return lastsep + 1;
460 	else
461 		return string;
462 }
463 
464 /*-----------------------------------------------------------------------*/
465 /**
466  * Close given internal file handle if it's still in use
467  * and (always) reset handle variables
468  */
GemDOS_CloseFileHandle(int i)469 static void GemDOS_CloseFileHandle(int i)
470 {
471 	if (FileHandles[i].bUsed)
472 		fclose(FileHandles[i].FileHandle);
473 	FileHandles[i].FileHandle = NULL;
474 	FileHandles[i].Basepage = 0;
475 	FileHandles[i].bUsed = false;
476 }
477 
478 /**
479  * Un-force given file handle
480  */
GemDOS_UnforceFileHandle(int i)481 static void GemDOS_UnforceFileHandle(int i)
482 {
483 	ForcedHandles[i].Handle = UNFORCED_HANDLE;
484 	ForcedHandles[i].Basepage = 0;
485 }
486 
487 /**
488  * Clear & un-force all file handles
489  */
GemDOS_ClearAllFileHandles(void)490 static void GemDOS_ClearAllFileHandles(void)
491 {
492 	int i;
493 
494 	for(i = 0; i < ARRAY_SIZE(FileHandles); i++)
495 	{
496 		GemDOS_CloseFileHandle(i);
497 	}
498 	for(i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
499 	{
500 		GemDOS_UnforceFileHandle(i);
501 	}
502 }
503 
504 /*-----------------------------------------------------------------------*/
505 
506 /**
507  * If program was executed, store path to it
508  * (should be called only by Fopen)
509  */
GemDOS_UpdateCurrentProgram(int Handle)510 static void GemDOS_UpdateCurrentProgram(int Handle)
511 {
512 	/* only first Fopen after Pexec needs to be handled */
513 	if (!PexecCalled)
514 		return;
515 	PexecCalled = false;
516 
517 	/* store program path */
518 	Symbols_ChangeCurrentProgram(FileHandles[Handle].szActualName);
519 }
520 
521 /*-----------------------------------------------------------------------*/
522 /**
523  * Initialize GemDOS/PC file system
524  */
GemDOS_Init(void)525 void GemDOS_Init(void)
526 {
527 	bInitGemDOS = false;
528 
529 	GemDOS_ClearAllFileHandles();
530 	GemDOS_ClearAllInternalDTAs();
531 
532 }
533 
534 /*-----------------------------------------------------------------------*/
535 /**
536  * Initialize GemDOS drives current paths (to drive root)
537  */
GemDOS_InitCurPaths(void)538 static void GemDOS_InitCurPaths(void)
539 {
540 	int i;
541 
542 	if (emudrives)
543 	{
544 		for (i = 0; i < MAX_HARDDRIVES; i++)
545 		{
546 			if (emudrives[i])
547 			{
548 				/* Initialize current directory to the root of the drive */
549 				strcpy(emudrives[i]->fs_currpath, emudrives[i]->hd_emulation_dir);
550 				File_AddSlashToEndFileName(emudrives[i]->fs_currpath);
551 			}
552 		}
553 	}
554 }
555 
556 /*-----------------------------------------------------------------------*/
557 /**
558  * Reset GemDOS file system
559  */
GemDOS_Reset(void)560 void GemDOS_Reset(void)
561 {
562 	GemDOS_Init();
563 	GemDOS_InitCurPaths();
564 
565 	/* Reset */
566 	act_pd = 0;
567 	CurrentDrive = nBootDrive;
568 	Symbols_RemoveCurrentProgram();
569 	INF_CreateOverride();
570 }
571 
572 /*-----------------------------------------------------------------------*/
573 /**
574  * Routine to check the Host OS HDD path for a Drive letter sub folder
575  */
GEMDOS_DoesHostDriveFolderExist(char * lpstrPath,int iDrive)576 static bool GEMDOS_DoesHostDriveFolderExist(char* lpstrPath, int iDrive)
577 {
578 	bool bExist = false;
579 
580 	Log_Printf(LOG_DEBUG, "Checking GEMDOS %c: HDD: %s\n", 'A'+iDrive, lpstrPath);
581 
582 	if (access(lpstrPath, F_OK) != 0 )
583 	{
584 		/* Try lower case drive letter instead */
585 		int	iIndex = strlen(lpstrPath)-1;
586 		lpstrPath[iIndex] = tolower((unsigned char)lpstrPath[iIndex]);
587 	}
588 
589 	/* Check if it's a HDD identifier (or other emulated device)
590 	 * and if the file/folder is accessible (security basis) */
591 	if (iDrive > 1 && access(lpstrPath, F_OK) == 0 )
592 	{
593 		struct stat status;
594 		if (stat(lpstrPath, &status) == 0 && (status.st_mode & S_IFDIR) != 0)
595 		{
596 			bExist = true;
597 		}
598 		else
599 		{
600 			Log_Printf(LOG_WARN, "Not suitable as GEMDOS HDD dir: %s\n", lpstrPath);
601 		}
602 	}
603 
604 	return bExist;
605 }
606 
607 
608 /**
609  * Determine upper limit of partitions that should be emulated.
610  *
611  * @return true if multiple GEMDOS partitions should be emulated, false otherwise
612  */
GemDOS_DetermineMaxPartitions(int * pnMaxDrives)613 static bool GemDOS_DetermineMaxPartitions(int *pnMaxDrives)
614 {
615 	struct dirent **files;
616 	int count, i, last;
617 	char letter;
618 	bool bMultiPartitions;
619 
620 	*pnMaxDrives = 0;
621 
622 	/* Scan through the main directory to see whether there are just single
623 	 * letter sub-folders there (then use multi-partition mode) or if
624 	 * arbitrary sub-folders are there (then use single-partition mode)
625 	 */
626 	count = scandir(ConfigureParams.HardDisk.szHardDiskDirectories[0], &files, 0, alphasort);
627 	if (count < 0)
628 	{
629 		Log_Printf(LOG_ERROR, "GEMDOS hard disk emulation failed:\n "
630 			   "Can not access '%s'.\n", ConfigureParams.HardDisk.szHardDiskDirectories[0]);
631 		return false;
632 	}
633 	else if (count <= 2)
634 	{
635 		/* Empty directory Only "." and ".."), assume single partition mode */
636 		last = 1;
637 		bMultiPartitions = false;
638 	}
639 	else
640 	{
641 		bMultiPartitions = true;
642 		/* Check all files in the directory */
643 		last = 0;
644 		for (i = 0; i < count; i++)
645 		{
646 			letter = toupper((unsigned char)files[i]->d_name[0]);
647 			if (!letter || letter == '.')
648 			{
649 				/* Ignore hidden files like "." and ".." */
650 				continue;
651 			}
652 
653 			if (letter < 'C' || letter > 'Z' || files[i]->d_name[1])
654 			{
655 				/* folder with name other than C-Z...
656 				 * (until Z under MultiTOS, to P otherwise)
657 				 * ... so use single partition mode! */
658 				last = 1;
659 				bMultiPartitions = false;
660 				break;
661 			}
662 
663 			/* alphasort isn't case insensitive */
664 			letter = letter - 'C' + 1;
665 			if (letter > last)
666 				last = letter;
667 		}
668 	}
669 
670 	if (last > MAX_HARDDRIVES)
671 		*pnMaxDrives = MAX_HARDDRIVES;
672 	else
673 		*pnMaxDrives = last;
674 
675 	/* Free file list */
676 	for (i = 0; i < count; i++)
677 		free(files[i]);
678 	free(files);
679 
680 	return bMultiPartitions;
681 }
682 
683 /*-----------------------------------------------------------------------*/
684 /**
685  * Initialize a GEMDOS drive.
686  * Supports up to MAX_HARDDRIVES HDD units.
687  */
GemDOS_InitDrives(void)688 void GemDOS_InitDrives(void)
689 {
690 	int i;
691 	int nMaxDrives;
692 	int DriveNumber;
693 	int SkipPartitions;
694 	int ImagePartitions;
695 	bool bMultiPartitions;
696 
697 	bMultiPartitions = GemDOS_DetermineMaxPartitions(&nMaxDrives);
698 
699 	/* intialize data for harddrive emulation: */
700 	if (nMaxDrives > 0 && !emudrives)
701 	{
702 		emudrives = calloc(MAX_HARDDRIVES, sizeof(EMULATEDDRIVE *));
703 		if (!emudrives)
704 		{
705 			perror("GemDOS_InitDrives");
706 			return;
707 		}
708 	}
709 
710 	ImagePartitions = nAcsiPartitions + nIDEPartitions;
711 	if (ConfigureParams.HardDisk.nGemdosDrive == DRIVE_SKIP)
712 		SkipPartitions = ImagePartitions;
713 	else
714 		SkipPartitions = ConfigureParams.HardDisk.nGemdosDrive;
715 
716 	/* Now initialize all available drives */
717 	for(i = 0; i < nMaxDrives; i++)
718 	{
719 		/* If single partition mode, skip to specified / first free drive */
720 		if (!bMultiPartitions)
721 		{
722 			i += SkipPartitions;
723 		}
724 
725 		/* Allocate emudrives entry for this drive */
726 		emudrives[i] = malloc(sizeof(EMULATEDDRIVE));
727 		if (!emudrives[i])
728 		{
729 			perror("GemDOS_InitDrives");
730 			continue;
731 		}
732 
733 		/* set emulation directory string */
734 		strcpy(emudrives[i]->hd_emulation_dir, ConfigureParams.HardDisk.szHardDiskDirectories[0]);
735 
736 		/* remove trailing slash, if any in the directory name */
737 		File_CleanFileName(emudrives[i]->hd_emulation_dir);
738 
739 		/* Add Requisit Folder ID */
740 		if (bMultiPartitions)
741 		{
742 			char sDriveLetter[] = { PATHSEP, (char)('C' + i), '\0' };
743 			strcat(emudrives[i]->hd_emulation_dir, sDriveLetter);
744 		}
745 		/* drive number (C: = 2, D: = 3, etc.) */
746 		DriveNumber = 2 + i;
747 
748 		// Check host file system to see if the drive folder for THIS
749 		// drive letter/number exists...
750 		if (GEMDOS_DoesHostDriveFolderExist(emudrives[i]->hd_emulation_dir, DriveNumber))
751 		{
752 			/* map drive */
753 			Log_Printf(LOG_INFO, "GEMDOS HDD emulation, %c: <-> %s.\n",
754 				   'A'+DriveNumber, emudrives[i]->hd_emulation_dir);
755 			emudrives[i]->drive_number = DriveNumber;
756 			nNumDrives = i + 3;
757 
758 			/* This letter may already be allocated to the one supported physical disk images
759 			 * (depends on how well Atari HD driver and Hatari interpretation of partition
760 			 *  table(s) match each other).
761 			 */
762 			if (i < ImagePartitions)
763 				Log_Printf(LOG_WARN, "GEMDOS HD drive %c: (may) override ACSI/IDE image partitions!\n", 'A'+DriveNumber);
764 		}
765 		else
766 		{
767 			free(emudrives[i]);	// Deallocate Memory (save space)
768 			emudrives[i] = NULL;
769 		}
770 	}
771 
772 	/* Set current paths in case Atari -> host GEMDOS path mapping
773 	 * is needed before TOS boots GEMDOS up (at which point they're
774 	 * also initialized), like happens with autostart INF file
775 	 * handling.
776 	 */
777 	GemDOS_InitCurPaths();
778 }
779 
780 
781 /*-----------------------------------------------------------------------*/
782 /**
783  * Un-init GEMDOS drives
784  */
GemDOS_UnInitDrives(void)785 void GemDOS_UnInitDrives(void)
786 {
787 	int i;
788 
789 	GemDOS_Reset();        /* Close all open files on emulated drive */
790 
791 	if (GEMDOS_EMU_ON)
792 	{
793 		for(i = 0; i < MAX_HARDDRIVES; i++)
794 		{
795 			if (emudrives[i])
796 			{
797 				free(emudrives[i]);    /* Release memory */
798 				emudrives[i] = NULL;
799 				nNumDrives -= 1;
800 			}
801 		}
802 
803 		free(emudrives);
804 		emudrives = NULL;
805 	}
806 }
807 
808 
809 /*-----------------------------------------------------------------------*/
810 /**
811  * Save file handle info.  If handle is used, save valid file modification
812  * timestamp and file position, otherwise dummies.
813  */
save_file_handle_info(FILE_HANDLE * handle)814 static void save_file_handle_info(FILE_HANDLE *handle)
815 {
816 	struct stat fstat;
817 	time_t mtime;
818 	off_t offset;
819 
820 	MemorySnapShot_Store(&handle->bUsed, sizeof(handle->bUsed));
821 	MemorySnapShot_Store(&handle->szMode, sizeof(handle->szMode));
822 	MemorySnapShot_Store(&handle->Basepage, sizeof(handle->Basepage));
823 	MemorySnapShot_Store(&handle->szActualName, sizeof(handle->szActualName));
824 	if (handle->bUsed)
825 	{
826 		offset = ftello(handle->FileHandle);
827 		stat(handle->szActualName, &fstat);
828 		mtime = fstat.st_mtime; /* modification time */
829 	}
830 	else
831 	{
832 		/* avoid warnings about access to undefined data */
833 		offset = 0;
834 		stat("/", &fstat);
835 		mtime = fstat.st_mtime;
836 	}
837 	MemorySnapShot_Store(&mtime, sizeof(mtime));
838 	MemorySnapShot_Store(&offset, sizeof(offset));
839 }
840 
841 /*-----------------------------------------------------------------------*/
842 /**
843  * Restore saved file handle info.  If handle is used, open file, validate
844  * that file modification timestamp matches, then seek to saved position.
845  * Restoring order must match one used in save_file_handle_info().
846  */
restore_file_handle_info(int i,FILE_HANDLE * handle)847 static void restore_file_handle_info(int i, FILE_HANDLE *handle)
848 {
849 	struct stat fstat;
850 	time_t mtime;
851 	off_t offset;
852 	FILE *fp;
853 
854 	if (handle->bUsed)
855 		fclose(handle->FileHandle);
856 
857 	/* read all to proceed correctly in snapshot */
858 	MemorySnapShot_Store(&handle->bUsed, sizeof(handle->bUsed));
859 	MemorySnapShot_Store(&handle->szMode, sizeof(handle->szMode));
860 	MemorySnapShot_Store(&handle->Basepage, sizeof(handle->Basepage));
861 	MemorySnapShot_Store(&handle->szActualName, sizeof(handle->szActualName));
862 	MemorySnapShot_Store(&mtime, sizeof(mtime));
863 	MemorySnapShot_Store(&offset, sizeof(offset));
864 	handle->FileHandle = NULL;
865 
866 	if (!handle->bUsed)
867 		return;
868 
869 	if (stat(handle->szActualName, &fstat) != 0)
870 	{
871 		handle->bUsed = false;
872 		Log_Printf(LOG_WARN, "GEMDOS handle %d cannot be restored, file missing: %s\n",
873 			   i, handle->szActualName);
874 		return;
875 	}
876 	/* assumes time_t is primitive type (unsigned long on Linux) */
877 	if (fstat.st_mtime != mtime)
878 	{
879 		Log_Printf(LOG_WARN, "restored GEMDOS handle %d points to a file that has been modified in meanwhile: %s\n",
880 			   i, handle->szActualName);
881 	}
882 	fp = fopen(handle->szActualName, handle->szMode);
883 	if (fp == NULL || fseeko(fp, offset, SEEK_SET) != 0)
884 	{
885 		handle->bUsed = false;
886 		Log_Printf(LOG_WARN, "GEMDOS '%s' handle %d cannot be restored, seek to saved offset %"PRId64" failed for: %s\n",
887 			   handle->szMode, i, offset, handle->szActualName);
888 		fclose(fp);
889 		return;
890 	}
891 	handle->FileHandle = fp;
892 }
893 
894 /*-----------------------------------------------------------------------*/
895 /**
896  * Save/Restore snapshot of local variables('MemorySnapShot_Store' handles type)
897  */
GemDOS_MemorySnapShot_Capture(bool bSave)898 void GemDOS_MemorySnapShot_Capture(bool bSave)
899 {
900 	FILE_HANDLE *finfo;
901 	int i, handles = ARRAY_SIZE(FileHandles);
902 	bool bEmudrivesAvailable;
903 
904 	/* Save/Restore the emudrives structure */
905 	bEmudrivesAvailable = (emudrives != NULL);
906 	MemorySnapShot_Store(&bEmudrivesAvailable, sizeof(bEmudrivesAvailable));
907 	if (bEmudrivesAvailable)
908 	{
909 		if (!emudrives)
910 		{
911 			/* As memory snapshot contained emulated drive(s),
912 			 * but currently there are none allocated yet...
913 			 * let's do it now!
914 			 */
915 			GemDOS_InitDrives();
916 		}
917 
918 		for(i = 0; i < MAX_HARDDRIVES; i++)
919 		{
920 			int bDummyDrive = false;
921 			if (!emudrives[i])
922 			{
923 				/* Allocate a dummy drive */
924 				emudrives[i] = malloc(sizeof(EMULATEDDRIVE));
925 				if (!emudrives[i])
926 				{
927 					perror("GemDOS_MemorySnapShot_Capture");
928 					continue;
929 				}
930 				memset(emudrives[i], 0, sizeof(EMULATEDDRIVE));
931 				bDummyDrive = true;
932 			}
933 			MemorySnapShot_Store(emudrives[i]->hd_emulation_dir,
934 			                     sizeof(emudrives[i]->hd_emulation_dir));
935 			MemorySnapShot_Store(emudrives[i]->fs_currpath,
936 			                     sizeof(emudrives[i]->fs_currpath));
937 			MemorySnapShot_Store(&emudrives[i]->drive_number,
938 			                     sizeof(emudrives[i]->drive_number));
939 			if (bDummyDrive)
940 			{
941 				free(emudrives[i]);
942 				emudrives[i] = NULL;
943 			}
944 		}
945 	}
946 
947 	/* misc information */
948 	MemorySnapShot_Store(&bInitGemDOS,sizeof(bInitGemDOS));
949 	MemorySnapShot_Store(&act_pd, sizeof(act_pd));
950 	MemorySnapShot_Store(&CurrentDrive, sizeof(CurrentDrive));
951 
952 	/* File handle related information */
953 	MemorySnapShot_Store(&ForcedHandles, sizeof(ForcedHandles));
954 	if (bSave)
955 	{
956 		MemorySnapShot_Store(&handles, sizeof(handles));
957 
958 		for (finfo = FileHandles, i = 0; i < handles; i++, finfo++)
959 			save_file_handle_info(finfo);
960 	}
961 	else
962 	{
963 		int saved_handles;
964 		MemorySnapShot_Store(&saved_handles, sizeof(saved_handles));
965 		assert(saved_handles == handles);
966 
967 		for (finfo = FileHandles, i = 0; i < handles; i++, finfo++)
968 			restore_file_handle_info(i, finfo);
969 
970 		/* DTA file name cache isn't valid anymore */
971 		GemDOS_ClearAllInternalDTAs();
972 	}
973 }
974 
975 
976 /*-----------------------------------------------------------------------*/
977 /**
978  * Return free PC file handle table index, or -1 if error
979  */
GemDOS_FindFreeFileHandle(void)980 static int GemDOS_FindFreeFileHandle(void)
981 {
982 	int i;
983 
984 	/* Scan our file list for free slot */
985 	for(i = 0; i < ARRAY_SIZE(FileHandles); i++)
986 	{
987 		if (!FileHandles[i].bUsed)
988 			return i;
989 	}
990 
991 	/* Cannot open any more files, return error */
992 	return -1;
993 }
994 
995 /*-----------------------------------------------------------------------*/
996 /**
997  * Check whether given basepage matches current program basepage
998  * or basepage for its parents.  If yes, return true, otherwise false.
999  */
GemDOS_BasepageMatches(Uint32 checkbase)1000 static bool GemDOS_BasepageMatches(Uint32 checkbase)
1001 {
1002 	int maxparents = 12; /* prevent basepage parent loops */
1003 	Uint32 basepage = STMemory_ReadLong(act_pd);
1004 	while (maxparents-- > 0 && STMemory_CheckAreaType(basepage, BASEPAGE_SIZE, ABFLAG_RAM))
1005 	{
1006 		if (basepage == checkbase)
1007 			return true;
1008 		basepage = STMemory_ReadLong(basepage + BASEPAGE_OFFSET_PARENT);
1009 	}
1010 	return false;
1011 }
1012 
1013 /**
1014  * Check whether TOS handle is within our table range, or aliased,
1015  * return (positive) internal Handle if yes, (negative) -1 for error.
1016  */
GemDOS_GetValidFileHandle(int Handle)1017 static int GemDOS_GetValidFileHandle(int Handle)
1018 {
1019 	int Forced = -1;
1020 
1021 	/* Has handle been aliased with Fforce()? */
1022 	if (Handle >= 0 && Handle < ARRAY_SIZE(ForcedHandles)
1023 	    && ForcedHandles[Handle].Handle != UNFORCED_HANDLE)
1024 	{
1025 		if (GemDOS_BasepageMatches(ForcedHandles[Handle].Basepage))
1026 		{
1027 			Forced = Handle;
1028 			Handle = ForcedHandles[Handle].Handle;
1029 		}
1030 		else
1031 		{
1032 			Log_Printf(LOG_WARN, "Removing (stale?) %d->%d file handle redirection.",
1033 				   Handle, ForcedHandles[Handle].Handle);
1034 			GemDOS_UnforceFileHandle(Handle);
1035 			return -1;
1036 		}
1037 	}
1038 	else
1039 	{
1040 		Handle -= BASE_FILEHANDLE;
1041 	}
1042 	/* handle is valid for current program and in our handle table? */
1043 	if (Handle >= 0 && Handle < ARRAY_SIZE(FileHandles)
1044 	    && FileHandles[Handle].bUsed)
1045 	{
1046 		Uint32 current = STMemory_ReadLong(act_pd);
1047 		if (FileHandles[Handle].Basepage == current || Forced >= 0)
1048 			return Handle;
1049 		/* bug in Atari program or in Hatari GEMDOS emu */
1050 		Log_Printf(LOG_WARN, "PREVENTED: program 0x%x accessing program 0x%x file handle %d.",
1051 			     current, FileHandles[Handle].Basepage, Handle);
1052 	}
1053 	/* invalid handle */
1054 	return -1;
1055 }
1056 
1057 /*-----------------------------------------------------------------------*/
1058 /**
1059  * Find drive letter from a filename, eg C,D... and return as drive ID(C:2, D:3...)
1060  * returns the current drive number if no drive is specified.  For special
1061  * devices (CON:, AUX:, PRN:), returns an invalid drive number.
1062  */
GemDOS_FindDriveNumber(char * pszFileName)1063 static int GemDOS_FindDriveNumber(char *pszFileName)
1064 {
1065 	/* Does have 'A:' or 'C:' etc.. at start of string? */
1066 	if (pszFileName[0] != '\0' && pszFileName[1] == ':')
1067 	{
1068 		char letter = toupper((unsigned char)pszFileName[0]);
1069 		if (letter >= 'A' && letter <= 'Z')
1070 			return (letter-'A');
1071 	}
1072 	else if (strlen(pszFileName) == 4 && pszFileName[3] == ':')
1073 	{
1074 		/* ':' can be used only as drive indicator, not otherwise,
1075 		 * so no need to check even special device name.
1076 		 */
1077 		return 0;
1078 	}
1079 	return CurrentDrive;
1080 }
1081 
1082 
1083 /**
1084  * Return true if drive ID (C:2, D:3 etc...) matches emulated hard-drive
1085  */
GemDOS_IsDriveEmulated(int drive)1086 bool GemDOS_IsDriveEmulated(int drive)
1087 {
1088 	drive -= 2;
1089 	if (drive < 0 || drive >= MAX_HARDDRIVES)
1090 		return false;
1091 	if (!(emudrives && emudrives[drive]))
1092 		return false;
1093 	assert(emudrives[drive]->drive_number == drive+2);
1094 	return true;
1095 }
1096 
1097 /*-----------------------------------------------------------------------*/
1098 /**
1099  * Return drive ID(C:2, D:3 etc...) or -1 if not one of our emulation hard-drives
1100  */
GemDOS_FileName2HardDriveID(char * pszFileName)1101 static int GemDOS_FileName2HardDriveID(char *pszFileName)
1102 {
1103 	/* Do we even have a hard-drive? */
1104 	if (GEMDOS_EMU_ON)
1105 	{
1106 		int DriveNumber;
1107 
1108 		/* Find drive letter (as number) */
1109 		DriveNumber = GemDOS_FindDriveNumber(pszFileName);
1110 		if (GemDOS_IsDriveEmulated(DriveNumber))
1111 			return DriveNumber;
1112 	}
1113 
1114 	/* Not a high-level redirected drive, let TOS handle it */
1115 	return -1;
1116 }
1117 
1118 
1119 /*-----------------------------------------------------------------------*/
1120 /**
1121  * Check whether a file in given path matches given case-insensitive pattern.
1122  * Return first matched name which caller needs to free, or NULL for no match.
1123  */
match_host_dir_entry(const char * path,const char * name,bool pattern)1124 static char* match_host_dir_entry(const char *path, const char *name, bool pattern)
1125 {
1126 #define MAX_UTF8_NAME_LEN (3*(8+1+3)+1) /* UTF-8 can have up to 3 bytes per character */
1127 	struct dirent *entry;
1128 	char *match = NULL;
1129 	DIR *dir;
1130 	char nameHost[MAX_UTF8_NAME_LEN];
1131 
1132 	Str_AtariToHost(name, nameHost, MAX_UTF8_NAME_LEN, INVALID_CHAR);
1133 	name = nameHost;
1134 
1135 	dir = opendir(path);
1136 	if (!dir)
1137 		return NULL;
1138 
1139 #if DEBUG_PATTERN_MATCH
1140 	fprintf(stderr, "DEBUG: GEMDOS match '%s'%s in '%s'", name, pattern?" (pattern)":"", path);
1141 #endif
1142 	if (pattern)
1143 	{
1144 		while ((entry = readdir(dir)))
1145 		{
1146 			Str_DecomposedToPrecomposedUtf8(entry->d_name, entry->d_name);   /* for OSX */
1147 			if (fsfirst_match(name, entry->d_name))
1148 			{
1149 				match = strdup(entry->d_name);
1150 				break;
1151 			}
1152 		}
1153 	}
1154 	else
1155 	{
1156 		while ((entry = readdir(dir)))
1157 		{
1158 			Str_DecomposedToPrecomposedUtf8(entry->d_name, entry->d_name);   /* for OSX */
1159 			if (strcasecmp(name, entry->d_name) == 0)
1160 			{
1161 				match = strdup(entry->d_name);
1162 				break;
1163 			}
1164 		}
1165 	}
1166 	closedir(dir);
1167 #if DEBUG_PATTERN_MATCH
1168 	fprintf(stderr, "-> '%s'\n", match);
1169 #endif
1170 	return match;
1171 }
1172 
1173 
to_same(int ch)1174 static int to_same(int ch)
1175 {
1176 	return ch;
1177 }
1178 
1179 /**
1180  * Clip given file name to 8+3 length like TOS does,
1181  * return resulting name length.
1182  */
clip_to_83(char * name)1183 static int clip_to_83(char *name)
1184 {
1185 	int diff, len;
1186 	char *dot;
1187 
1188 	dot = strchr(name, '.');
1189 	if (dot) {
1190 		diff = strlen(dot) - 4;
1191 		if (diff > 0)
1192 		{
1193 			Log_Printf(LOG_WARN, "have to clip %d chars from '%s' extension!\n", diff, name);
1194 			dot[4] = '\0';
1195 		}
1196 		diff = dot - name - 8;
1197 		if (diff > 0)
1198 		{
1199 			Log_Printf(LOG_WARN, "have to clip %d chars from '%s' base!\n", diff, name);
1200 			memmove(name + 8, dot, strlen(dot) + 1);
1201 		}
1202 		return strlen(name);
1203 	}
1204 	len = strlen(name);
1205 	if (len > 8)
1206 	{
1207 		Log_Printf(LOG_WARN, "have to clip %d chars from '%s'!\n", len - 8, name);
1208 		name[8] = '\0';
1209 		len = 8;
1210 	}
1211 	return len;
1212 }
1213 
1214 /*-----------------------------------------------------------------------*/
1215 /**
1216  * Check whether given TOS file/dir exists in given host path.
1217  * If it does, add the matched host filename to the given path,
1218  * otherwise add the given filename as is to it.  Guarantees
1219  * that the resulting string doesn't exceed maxlen+1.
1220  *
1221  * Return true if match found, false otherwise.
1222  */
add_path_component(char * path,int maxlen,const char * origname,bool is_dir)1223 static bool add_path_component(char *path, int maxlen, const char *origname, bool is_dir)
1224 {
1225 	char *tmp, *match;
1226 	int dot, namelen, pathlen;
1227 	int (*chr_conv)(int);
1228 	bool modified;
1229 	char *name = alloca(strlen(origname) + 3);
1230 
1231 	/* append separator */
1232 	pathlen = strlen(path);
1233 	if (pathlen >= maxlen)
1234 		return false;
1235 	path[pathlen++] = PATHSEP;
1236 	path[pathlen] = '\0';
1237 
1238 	/* TOS clips names to 8+3 length */
1239 	strcpy(name, origname);
1240 	namelen = clip_to_83(name);
1241 
1242 	/* first try exact (case insensitive) match */
1243 	match = match_host_dir_entry(path, name, false);
1244 	if (match)
1245 	{
1246 		/* use strncat so that string is always nul terminated */
1247 		strncat(path+pathlen, match, maxlen-pathlen);
1248 		free(match);
1249 		return true;
1250 	}
1251 
1252 	/* Here comes a work-around for a bug in the file selector
1253 	 * of TOS 1.02: When a folder name has exactly 8 characters,
1254 	 * it appends a '.' at the end of the name...
1255 	 */
1256 	if (is_dir && namelen == 9 && name[8] == '.')
1257 	{
1258 		name[8] = '\0';
1259 		match = match_host_dir_entry(path, name, false);
1260 		if (match)
1261 		{
1262 			strncat(path+pathlen, match, maxlen-pathlen);
1263 			free(match);
1264 			return true;
1265 		}
1266 	}
1267 
1268 	/* Assume there were invalid characters or that the host file
1269 	 * was too long to fit into GEMDOS 8+3 filename limits.
1270 	 * If that's the case, modify the name to a pattern that
1271 	 * will match such host files and try again.
1272 	 */
1273 	modified = false;
1274 
1275 	/* catch potentially invalid characters */
1276 	for (tmp = name; *tmp; tmp++)
1277 	{
1278 		if (*tmp == INVALID_CHAR)
1279 		{
1280 			*tmp = '?';
1281 			modified = true;
1282 		}
1283 	}
1284 
1285 	/* catch potentially too long extension */
1286 	for (dot = 0; name[dot] && name[dot] != '.'; dot++);
1287 	if (namelen - dot > 3)
1288 	{
1289 		dot++;
1290 		/* "emulated.too" -> "emulated.too*" */
1291 		name[namelen++] = '*';
1292 		name[namelen] = '\0';
1293 		modified = true;
1294 	}
1295 	/* catch potentially too long part before extension */
1296 	if (namelen > 8 && name[8] == '.')
1297 	{
1298 		dot++;
1299 		/* "emulated.too*" -> "emulated*.too*" */
1300 		memmove(name+9, name+8, namelen-7);
1301 		namelen++;
1302 		name[8] = '*';
1303 		modified = true;
1304 	}
1305 	/* catch potentially too long part without extension */
1306 	else if (namelen == 8 && !name[dot])
1307 	{
1308 		/* "emulated" -> "emulated*" */
1309 		name[8] = '*';
1310 		name[9] = '\0';
1311 		namelen++;
1312 		modified = true;
1313 	}
1314 
1315 	if (modified)
1316 	{
1317 		match = match_host_dir_entry(path, name, true);
1318 		if (match)
1319 		{
1320 			strncat(path+pathlen, match, maxlen-pathlen);
1321 			free(match);
1322 			return true;
1323 		}
1324 	}
1325 
1326 	/* not found, copy file/dirname as is */
1327 	switch (ConfigureParams.HardDisk.nGemdosCase) {
1328 	case GEMDOS_UPPER:
1329 		chr_conv = toupper;
1330 		break;
1331 	case GEMDOS_LOWER:
1332 		chr_conv = tolower;
1333 		break;
1334 	default:
1335 		chr_conv = to_same;
1336 	}
1337 	tmp = name;
1338 	while (*origname)
1339 		*tmp++ = chr_conv(*origname++);
1340 	*tmp = '\0';
1341 	/* strncat(path+pathlen, name, maxlen-pathlen); */
1342 	Str_AtariToHost(name, path+pathlen, maxlen-pathlen, INVALID_CHAR);
1343 	return false;
1344 }
1345 
1346 
1347 /**
1348  * Join remaining path without matching. This helper is used after host
1349  * file name matching fails, to append the failing part of the TOS path
1350  * to the host path, so that it won't be a valid host path.
1351  *
1352  * Specifically, the path separators need to be converted, otherwise things
1353  * like Fcreate() could create files that have TOS directory names as part
1354  * of file names on Unix (as \ is valid filename char on Unix).  Fcreate()
1355  * needs to create them only when just the file name isn't found, but all
1356  * the directory components have.
1357  */
add_remaining_path(const char * src,char * dstpath,int dstlen)1358 static void add_remaining_path(const char *src, char *dstpath, int dstlen)
1359 {
1360 	char *dst;
1361 	int i = strlen(dstpath);
1362 
1363 	Str_AtariToHost(src, dstpath+i, dstlen-i, INVALID_CHAR);
1364 
1365 	for (dst = dstpath + i; *dst; dst++)
1366 		if (*dst == '\\')
1367 			*dst = PATHSEP;
1368 }
1369 
1370 
1371 /*-----------------------------------------------------------------------*/
1372 /**
1373  * Use hard-drive directory, current ST directory and filename
1374  * to create correct path to host file system.  If given filename
1375  * isn't found on host file system, just append GEMDOS filename
1376  * to the path as is.
1377  *
1378  * TODO: currently there are many callers which give this dest buffer of
1379  * MAX_GEMDOS_PATH size i.e. don't take into account that host filenames
1380  * can be up to FILENAME_MAX long.  Plain GEMDOS paths themselves may be
1381  * MAX_GEMDOS_PATH long even before host dir is prepended to it!
1382  * Way forward: allocate the host path here as FILENAME_MAX so that
1383  * it's always long enough and let callers free it. Assert if alloc
1384  * fails so that callers' don't need to.
1385  */
GemDOS_CreateHardDriveFileName(int Drive,const char * pszFileName,char * pszDestName,int nDestNameLen)1386 void GemDOS_CreateHardDriveFileName(int Drive, const char *pszFileName,
1387                                     char *pszDestName, int nDestNameLen)
1388 {
1389 	const char *s, *filename = pszFileName;
1390 	int minlen;
1391 
1392 	/* make sure that more convenient strncat() can be used on the
1393 	 * destination string (it always null terminates unlike strncpy()) */
1394 	*pszDestName = 0;
1395 
1396 	/* Is it a valid hard drive? */
1397 	assert(GemDOS_IsDriveEmulated(Drive));
1398 
1399 	/* Check for valid string */
1400 	if (filename[0] == '\0')
1401 		return;
1402 
1403 	/* strcat writes n+1 chars, so decrease len */
1404 	nDestNameLen--;
1405 
1406 	/* full filename with drive "C:\foo\bar" */
1407 	if (filename[1] == ':')
1408 	{
1409 		strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen);
1410 		filename += 2;
1411 	}
1412 	/* filename referenced from root: "\foo\bar" */
1413 	else if (filename[0] == '\\')
1414 	{
1415 		strncat(pszDestName, emudrives[Drive-2]->hd_emulation_dir, nDestNameLen);
1416 	}
1417 	/* filename relative to current directory */
1418 	else
1419 	{
1420 		strncat(pszDestName, emudrives[Drive-2]->fs_currpath, nDestNameLen);
1421 	}
1422 
1423 	minlen = strlen(emudrives[Drive-2]->hd_emulation_dir);
1424 	/* this doesn't take into account possible long host filenames
1425 	 * that will make dest name longer than pszFileName 8.3 paths,
1426 	 * or GEMDOS paths using "../" which make it smaller.  Both
1427 	 * should(?) be rare in paths, so this info to user should be
1428 	 * good enough.
1429 	 */
1430 	if (nDestNameLen < minlen + (int)strlen(pszFileName) + 2)
1431 	{
1432 		Log_AlertDlg(LOG_ERROR, "Appending GEMDOS path '%s' to HDD emu host root dir doesn't fit to %d chars (current Hatari limit)!",
1433 			     pszFileName, nDestNameLen);
1434 		add_remaining_path(filename, pszDestName, nDestNameLen);
1435 		return;
1436 	}
1437 
1438 	/* "../" handling breaks if there are extra slashes */
1439 	File_CleanFileName(pszDestName);
1440 
1441 	/* go through path directory components, advancing 'filename'
1442 	 * pointer while parsing them.
1443 	 */
1444 	for (;;)
1445 	{
1446 		/* skip extra path separators */
1447 		while (*filename == '\\')
1448 			filename++;
1449 
1450 		// fprintf(stderr, "filename: '%s', path: '%s'\n", filename, pszDestName);
1451 
1452 		/* skip "." references to current directory */
1453 		if (filename[0] == '.' &&
1454 		    (filename[1] == '\\' || !filename[1]))
1455 		{
1456 			filename++;
1457 			continue;
1458 		}
1459 
1460 		/* ".." path component -> strip last dir from dest path */
1461 		if (filename[0] == '.' &&
1462 		    filename[1] == '.' &&
1463 		    (filename[2] == '\\' || !filename[2]))
1464 		{
1465 			char *sep = strrchr(pszDestName, PATHSEP);
1466 			if (sep)
1467 			{
1468 				if (sep - pszDestName < minlen)
1469 					Log_Printf(LOG_WARN, "GEMDOS path '%s' tried to back out of GEMDOS drive!\n", pszFileName);
1470 				else
1471 					*sep = '\0';
1472 			}
1473 			filename += 2;
1474 			continue;
1475 		}
1476 
1477 		/* handle directory component */
1478 		if ((s = strchr(filename, '\\')))
1479 		{
1480 			int dirlen = s - filename;
1481 			char *dirname = alloca(dirlen + 1);
1482 			/* copy dirname */
1483 			strncpy(dirname, filename, dirlen);
1484 			dirname[dirlen] = '\0';
1485 			/* and advance filename */
1486 			filename = s;
1487 
1488 			if (strchr(dirname, '?') || strchr(dirname, '*'))
1489 				Log_Printf(LOG_WARN, "GEMDOS dir name '%s' with wildcards in %s!\n", dirname, pszFileName);
1490 
1491 			/* convert and append dirname to host path */
1492 			if (!add_path_component(pszDestName, nDestNameLen, dirname, true))
1493 			{
1494 				Log_Printf(LOG_WARN, "No GEMDOS dir '%s'\n", pszDestName);
1495 				add_remaining_path(filename, pszDestName, nDestNameLen);
1496 				return;
1497 			}
1498 			continue;
1499 		}
1500 
1501 		/* path directory components done */
1502 		break;
1503 	}
1504 
1505 	if (*filename)
1506 	{
1507 		/* a wildcard instead of a complete file name? */
1508 		if (strchr(filename,'?') || strchr(filename,'*'))
1509 		{
1510 			int len = strlen(pszDestName);
1511 			if (len < nDestNameLen)
1512 			{
1513 				pszDestName[len++] = PATHSEP;
1514 				pszDestName[len] = '\0';
1515 			}
1516 			/* use strncat so that string is always nul terminated */
1517 			/* strncat(pszDestName+len, filename, nDestNameLen-len); */
1518 			Str_AtariToHost(filename, pszDestName+len, nDestNameLen-len, INVALID_CHAR);
1519 		}
1520 		else if (!add_path_component(pszDestName, nDestNameLen, filename, false))
1521 		{
1522 			/* It's often normal, that GEM uses this to test for
1523 			 * existence of desktop.inf or newdesk.inf for example.
1524 			 */
1525 			LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS didn't find filename %s\n", pszDestName);
1526 			return;
1527 		}
1528 	}
1529 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS: %s -> host: %s\n", pszFileName, pszDestName);
1530 }
1531 
1532 
1533 /**
1534  * GEMDOS Cconws
1535  * Call 0x9
1536  */
GemDOS_Cconws(Uint32 Params)1537 static bool GemDOS_Cconws(Uint32 Params)
1538 {
1539 	Uint32 Addr;
1540 	char *pBuffer;
1541 
1542 	Addr = STMemory_ReadLong(Params);
1543 
1544 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x9 Cconws(0x%X) at PC 0x%X\n",
1545 		  Addr, CallingPC);
1546 
1547 	/* We only intercept this call in non-TOS mode */
1548 	if (bUseTos)
1549 		return false;
1550 
1551 	/* Check that write is from valid memory area */
1552 	if ((CallingPC < TosAddress || CallingPC >= TosAddress + TosSize)
1553 	    && !STMemory_CheckAreaType(Addr, 80 * 25, ABFLAG_RAM))
1554 	{
1555 		Log_Printf(LOG_WARN, "GEMDOS Cconws() failed due to invalid RAM range at 0x%x\n", Addr);
1556 		Regs[REG_D0] = GEMDOS_ERANGE;
1557 		return true;
1558 	}
1559 
1560 	pBuffer = (char *)STMemory_STAddrToPointer(Addr);
1561 	if (fwrite(pBuffer, strnlen(pBuffer, 80 * 25), 1, stdout) < 1)
1562 		Regs[REG_D0] = GEMDOS_ERROR;
1563 	else
1564 		Regs[REG_D0] = GEMDOS_EOK;
1565 
1566 	return true;
1567 }
1568 
1569 /*-----------------------------------------------------------------------*/
1570 /**
1571  * GEMDOS Set drive (0=A,1=B,2=C etc...)
1572  * Call 0xE
1573  */
GemDOS_SetDrv(Uint32 Params)1574 static bool GemDOS_SetDrv(Uint32 Params)
1575 {
1576 	/* Read details from stack for our own use */
1577 	CurrentDrive = STMemory_ReadWord(Params);
1578 
1579 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x0E Dsetdrv(0x%x) at PC=0x%X\n", (int)CurrentDrive,
1580 		  CallingPC);
1581 
1582 	/* Still re-direct to TOS */
1583 	return false;
1584 }
1585 
1586 
1587 /*-----------------------------------------------------------------------*/
1588 /**
1589  * GEMDOS Dfree Free disk space.
1590  * Call 0x36
1591  */
GemDOS_DFree(Uint32 Params)1592 static bool GemDOS_DFree(Uint32 Params)
1593 {
1594 #ifdef HAVE_STATVFS
1595 	struct statvfs buf;
1596 #endif
1597 	int Drive, Total, Free;
1598 	Uint32 Address;
1599 
1600 	Address = STMemory_ReadLong(Params);
1601 	Drive = STMemory_ReadWord(Params+SIZE_LONG);
1602 
1603 	/* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */
1604 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x36 Dfree(0x%x, %i) at PC 0x%X\n", Address, Drive,
1605 		  CallingPC);
1606 	if (Drive == 0)
1607 		Drive = CurrentDrive;
1608 	else
1609 		Drive--;
1610 
1611 	/* is it our drive? */
1612 	if (!GemDOS_IsDriveEmulated(Drive))
1613 	{
1614 		/* no, redirect to TOS */
1615 		return false;
1616 	}
1617 	/* Check that write is requested to valid memory area */
1618 	if ( !STMemory_CheckAreaType ( Address, 16, ABFLAG_RAM ) )
1619 	{
1620 		Log_Printf(LOG_WARN, "GEMDOS Dfree() failed due to invalid RAM range 0x%x+%i\n", Address, 16);
1621 		Regs[REG_D0] = GEMDOS_ERANGE;
1622 		return true;
1623 	}
1624 
1625 #ifdef HAVE_STATVFS
1626 	if (statvfs(emudrives[Drive-2]->hd_emulation_dir, &buf) == 0)
1627 	{
1628 		Total = buf.f_blocks/1024 * buf.f_frsize;
1629 		if (buf.f_bavail)
1630 			Free = buf.f_bavail;	/* free for unprivileged user */
1631 		else
1632 			Free = buf.f_bfree;
1633 		Free = Free/1024 * buf.f_bsize;
1634 
1635 		/* TOS version limits based on:
1636 		 *   http://hddriver.seimet.de/en/faq.html
1637 		 */
1638 		if (TosVersion >= 0x0400)
1639 		{
1640 			if (Total > 1024*1024)
1641 				Total = 1024*1024;
1642 		}
1643 		else
1644 		{
1645 			if (TosVersion >= 0x0106)
1646 			{
1647 				if (Total > 512*1024)
1648 					Total = 512*1024;
1649 			}
1650 			else
1651 			{
1652 				if (Total > 256*1024)
1653 					Total = 256*1024;
1654 			}
1655 		}
1656 		if (Free > Total)
1657 			Free = Total;
1658 	}
1659 	else
1660 #endif
1661 	{
1662 		/* fake 32MB drive with 16MB free */
1663 		Total = 32*1024;
1664 		Free = 16*1024;
1665 	}
1666 	STMemory_WriteLong(Address,  Free);             /* free clusters */
1667 	STMemory_WriteLong(Address+SIZE_LONG, Total);   /* total clusters */
1668 
1669 	STMemory_WriteLong(Address+SIZE_LONG*2, 512);   /* bytes per sector */
1670 	STMemory_WriteLong(Address+SIZE_LONG*3, 2);     /* sectors per cluster (cluster = 1KB) */
1671 	Regs[REG_D0] = GEMDOS_EOK;
1672 	return true;
1673 }
1674 
1675 
1676 
1677 /*-----------------------------------------------------------------------*/
1678 /**
1679  * Helper to map Unix errno to GEMDOS error value
1680  */
1681 typedef enum {
1682 	ERROR_FILE,
1683 	ERROR_PATH
1684 } etype_t;
1685 
errno2gemdos(const int error,const etype_t etype)1686 static Uint32 errno2gemdos(const int error, const etype_t etype)
1687 {
1688 	LOG_TRACE(TRACE_OS_GEMDOS, "-> ERROR (errno = %d)\n", error);
1689 	switch (error)
1690 	{
1691 	case ENOENT:
1692 		if (etype == ERROR_FILE)
1693 			return GEMDOS_EFILNF;/* File not found */
1694 	case ENOTDIR:
1695 		return GEMDOS_EPTHNF;        /* Path not found */
1696 	case ENOTEMPTY:
1697 	case EEXIST:
1698 	case EPERM:
1699 	case EACCES:
1700 	case EROFS:
1701 		return GEMDOS_EACCDN;        /* Access denied */
1702 	default:
1703 		return GEMDOS_ERROR;         /* Misc error */
1704 	}
1705 }
1706 
1707 /*-----------------------------------------------------------------------*/
1708 /**
1709  * GEMDOS MkDir
1710  * Call 0x39
1711  */
GemDOS_MkDir(Uint32 Params)1712 static bool GemDOS_MkDir(Uint32 Params)
1713 {
1714 	char *pDirName, *psDirPath;
1715 	int Drive;
1716 
1717 	/* Find directory to make */
1718 	pDirName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1719 
1720 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x39 Dcreate(\"%s\") at PC 0x%X\n", pDirName,
1721 		  CallingPC);
1722 
1723 	Drive = GemDOS_FileName2HardDriveID(pDirName);
1724 
1725 	if (!ISHARDDRIVE(Drive))
1726 	{
1727 		/* redirect to TOS */
1728 		return false;
1729 	}
1730 
1731 	/* write protected device? */
1732 	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
1733 	{
1734 		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Dcreate(\"%s\")\n", pDirName);
1735 		Regs[REG_D0] = GEMDOS_EWRPRO;
1736 		return true;
1737 	}
1738 
1739 	psDirPath = malloc(FILENAME_MAX);
1740 	if (!psDirPath)
1741 	{
1742 		perror("GemDOS_MkDir");
1743 		Regs[REG_D0] = GEMDOS_ENSMEM;
1744 		return true;
1745 	}
1746 
1747 	/* Copy old directory, as if calls fails keep this one */
1748 	GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX);
1749 
1750 	/* Attempt to make directory */
1751 	if (mkdir(psDirPath, 0755) == 0)
1752 		Regs[REG_D0] = GEMDOS_EOK;
1753 	else
1754 		Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH);
1755 	free(psDirPath);
1756 	return true;
1757 }
1758 
1759 /*-----------------------------------------------------------------------*/
1760 /**
1761  * GEMDOS RmDir
1762  * Call 0x3A
1763  */
GemDOS_RmDir(Uint32 Params)1764 static bool GemDOS_RmDir(Uint32 Params)
1765 {
1766 	char *pDirName, *psDirPath;
1767 	int Drive;
1768 
1769 	/* Find directory to make */
1770 	pDirName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1771 
1772 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3A Ddelete(\"%s\") at PC 0x%X\n", pDirName,
1773 		  CallingPC);
1774 
1775 	Drive = GemDOS_FileName2HardDriveID(pDirName);
1776 
1777 	if (!ISHARDDRIVE(Drive))
1778 	{
1779 		/* redirect to TOS */
1780 		return false;
1781 	}
1782 
1783 	/* write protected device? */
1784 	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
1785 	{
1786 		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Ddelete(\"%s\")\n", pDirName);
1787 		Regs[REG_D0] = GEMDOS_EWRPRO;
1788 		return true;
1789 	}
1790 
1791 	psDirPath = malloc(FILENAME_MAX);
1792 	if (!psDirPath)
1793 	{
1794 		perror("GemDOS_RmDir");
1795 		Regs[REG_D0] = GEMDOS_ENSMEM;
1796 		return true;
1797 	}
1798 
1799 	/* Copy old directory, as if calls fails keep this one */
1800 	GemDOS_CreateHardDriveFileName(Drive, pDirName, psDirPath, FILENAME_MAX);
1801 
1802 	/* Attempt to remove directory */
1803 	if (rmdir(psDirPath) == 0)
1804 		Regs[REG_D0] = GEMDOS_EOK;
1805 	else
1806 		Regs[REG_D0] = errno2gemdos(errno, ERROR_PATH);
1807 	free(psDirPath);
1808 	return true;
1809 }
1810 
1811 
1812 /*-----------------------------------------------------------------------*/
1813 /**
1814  * GEMDOS ChDir
1815  * Call 0x3B
1816  */
GemDOS_ChDir(Uint32 Params)1817 static bool GemDOS_ChDir(Uint32 Params)
1818 {
1819 	char *pDirName, *psTempDirPath;
1820 	struct stat buf;
1821 	int Drive;
1822 
1823 	/* Find new directory */
1824 	pDirName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1825 
1826 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3B Dsetpath(\"%s\") at PC 0x%X\n", pDirName,
1827 		  CallingPC);
1828 
1829 	Drive = GemDOS_FileName2HardDriveID(pDirName);
1830 
1831 	if (!ISHARDDRIVE(Drive))
1832 	{
1833 		/* redirect to TOS */
1834 		return false;
1835 	}
1836 
1837 	/* Allocate temporary memory for path name: */
1838 	psTempDirPath = malloc(FILENAME_MAX);
1839 	if (!psTempDirPath)
1840 	{
1841 		perror("GemDOS_ChDir");
1842 		Regs[REG_D0] = GEMDOS_ENSMEM;
1843 		return true;
1844 	}
1845 
1846 	GemDOS_CreateHardDriveFileName(Drive, pDirName, psTempDirPath, FILENAME_MAX);
1847 
1848 	/* Remove trailing slashes (stat on Windows does not like that) */
1849 	File_CleanFileName(psTempDirPath);
1850 
1851 	if (stat(psTempDirPath, &buf))
1852 	{
1853 		/* error */
1854 		free(psTempDirPath);
1855 		Regs[REG_D0] = GEMDOS_EPTHNF;
1856 		return true;
1857 	}
1858 
1859 	File_AddSlashToEndFileName(psTempDirPath);
1860 	File_MakeAbsoluteName(psTempDirPath);
1861 
1862 	/* Prevent '..' commands moving BELOW the root HDD folder */
1863 	/* by double checking if path is valid */
1864 	if (strncmp(psTempDirPath, emudrives[Drive-2]->hd_emulation_dir,
1865 		    strlen(emudrives[Drive-2]->hd_emulation_dir)) == 0)
1866 	{
1867 		strlcpy(emudrives[Drive-2]->fs_currpath, psTempDirPath,
1868 		        sizeof(emudrives[Drive-2]->fs_currpath));
1869 		Regs[REG_D0] = GEMDOS_EOK;
1870 	}
1871 	else
1872 	{
1873 		Regs[REG_D0] = GEMDOS_EPTHNF;
1874 	}
1875 	free(psTempDirPath);
1876 
1877 	return true;
1878 
1879 }
1880 
1881 
1882 /*-----------------------------------------------------------------------*/
1883 /**
1884  * Helper to check whether given file's path is missing.
1885  * Returns true if missing, false if found.
1886  * Modifies the argument buffer.
1887  */
GemDOS_FilePathMissing(char * szActualFileName)1888 static bool GemDOS_FilePathMissing(char *szActualFileName)
1889 {
1890 	char *ptr = strrchr(szActualFileName, PATHSEP);
1891 	if (ptr)
1892 	{
1893 		*ptr = 0;   /* Strip filename from string */
1894 		if (!File_DirExists(szActualFileName))
1895 			return true;
1896 	}
1897 	return false;
1898 }
1899 
1900 
1901 /*-----------------------------------------------------------------------*/
redirect_to_TOS(void)1902 static inline bool redirect_to_TOS(void)
1903 {
1904 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> to TOS\n");
1905 	return false;
1906 }
1907 
1908 /*-----------------------------------------------------------------------*/
1909 /**
1910  * GEMDOS Create file
1911  * Call 0x3C
1912  */
GemDOS_Create(Uint32 Params)1913 static bool GemDOS_Create(Uint32 Params)
1914 {
1915 	/* TODO: host filenames might not fit into this */
1916 	char szActualFileName[MAX_GEMDOS_PATH];
1917 	char *pszFileName;
1918 	int Drive,Index, Mode;
1919 
1920 	/* Find filename */
1921 	pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
1922 	Mode = STMemory_ReadWord(Params+SIZE_LONG);
1923 
1924 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
1925 		  "GEMDOS 0x3C Fcreate(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, Mode,
1926 		  CallingPC);
1927 
1928 	Drive = GemDOS_FileName2HardDriveID(pszFileName);
1929 
1930 	if (!ISHARDDRIVE(Drive))
1931 	{
1932 		/* redirect to TOS */
1933 		return redirect_to_TOS();
1934 	}
1935 
1936 	if (Mode == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
1937 	{
1938 		Log_Printf(LOG_WARN, "Warning: Hatari doesn't support GEMDOS volume"
1939 			   " label setting\n(for '%s')\n", pszFileName);
1940 		Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
1941 		return true;
1942 	}
1943 
1944 	/* write protected device? */
1945 	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
1946 	{
1947 		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fcreate(\"%s\")\n", pszFileName);
1948 		Regs[REG_D0] = GEMDOS_EWRPRO;
1949 		return true;
1950 	}
1951 
1952 	/* Now convert to hard drive filename */
1953 	GemDOS_CreateHardDriveFileName(Drive, pszFileName,
1954 	                            szActualFileName, sizeof(szActualFileName));
1955 
1956 	/* Find slot to store file handle, as need to return WORD handle for ST */
1957 	Index = GemDOS_FindFreeFileHandle();
1958 	if (Index == -1)
1959 	{
1960 		/* No free handles, return error code */
1961 		Regs[REG_D0] = GEMDOS_ENHNDL;       /* No more handles */
1962 		return true;
1963 	}
1964 
1965 	/* truncate and open for reading & writing */
1966 	FileHandles[Index].FileHandle = fopen(szActualFileName, "wb+");
1967 
1968 	if (FileHandles[Index].FileHandle != NULL)
1969 	{
1970 		/* FIXME: implement other Mode attributes
1971 		 * - GEMDOS_FILE_ATTRIB_HIDDEN       (FA_HIDDEN)
1972 		 * - GEMDOS_FILE_ATTRIB_SYSTEM_FILE  (FA_SYSTEM)
1973 		 * - GEMDOS_FILE_ATTRIB_SUBDIRECTORY (FA_DIR)
1974 		 * - GEMDOS_FILE_ATTRIB_WRITECLOSE   (FA_ARCHIVE)
1975 		 *   (set automatically by GemDOS >= 0.15)
1976 		 */
1977 		if (Mode & GEMDOS_FILE_ATTRIB_READONLY)
1978 		{
1979 			/* after closing, file should be read-only */
1980 			if (chmod(szActualFileName, S_IRUSR|S_IRGRP|S_IROTH))
1981 			{
1982 				perror("Failed to set file to read-only");
1983 			}
1984 		}
1985 		/* Tag handle table entry as used in this process and return handle */
1986 		FileHandles[Index].bUsed = true;
1987 		strcpy(FileHandles[Index].szMode, "wb+");
1988 		FileHandles[Index].Basepage = STMemory_ReadLong(act_pd);
1989 		snprintf(FileHandles[Index].szActualName,
1990 			 sizeof(FileHandles[Index].szActualName),
1991 			 "%s", szActualFileName);
1992 
1993 		/* Return valid ST file handle from our range (from BASE_FILEHANDLE upwards) */
1994 		Regs[REG_D0] = Index+BASE_FILEHANDLE;
1995 		LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> FD %d (%s)\n", Regs[REG_D0],
1996 			  Mode & GEMDOS_FILE_ATTRIB_READONLY ? "read-only":"read/write");
1997 		return true;
1998 	}
1999 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR (errno = %d)\n", errno);
2000 
2001 	/* We failed to create the file, did we have required access rights? */
2002 	if (errno == EACCES || errno == EROFS ||
2003 	    errno == EPERM || errno == EISDIR)
2004 	{
2005 		Log_Printf(LOG_WARN, "GEMDOS failed to create/truncate '%s'\n",
2006 			   szActualFileName);
2007 		Regs[REG_D0] = GEMDOS_EACCDN;
2008 		return true;
2009 	}
2010 
2011 	/* Or was path to file missing? (ST-Zip 2.6 relies on getting
2012 	 * correct error about that during extraction of ZIP files.)
2013 	 */
2014 	if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName))
2015 	{
2016 		Regs[REG_D0] = GEMDOS_EPTHNF; /* Path not found */
2017 		return true;
2018 	}
2019 
2020 	Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
2021 	return true;
2022 }
2023 
2024 
2025 /**
2026  * GEMDOS Open file
2027  * Call 0x3D
2028  */
GemDOS_Open(Uint32 Params)2029 static bool GemDOS_Open(Uint32 Params)
2030 {
2031 	/* TODO: host filenames might not fit into this */
2032 	char szActualFileName[MAX_GEMDOS_PATH];
2033 	char *pszFileName;
2034 	const char *ModeStr, *RealMode;
2035 	const char *Modes[] = {
2036 		"read-only", "write-only", "read/write", "read/write"
2037 	};
2038 	int Drive, Index, Mode;
2039 	FILE *OverrideHandle;
2040 	bool bToTos = false;
2041 
2042 	/* Find filename */
2043 	pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2044 	Mode = STMemory_ReadWord(Params+SIZE_LONG);
2045 	Mode &= 3;
2046 
2047 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
2048 		  "GEMDOS 0x3D Fopen(\"%s\", %s) at PC=0x%X\n",
2049 		  pszFileName, Modes[Mode], CallingPC);
2050 
2051 	Drive = GemDOS_FileName2HardDriveID(pszFileName);
2052 
2053 	if (!ISHARDDRIVE(Drive))
2054 	{
2055 		if (INF_Overriding(AUTOSTART_FOPEN))
2056 			bToTos = true;
2057 		else
2058 			return redirect_to_TOS();
2059 	}
2060 
2061 	/* Find slot to store file handle, as need to return WORD handle for ST  */
2062 	Index = GemDOS_FindFreeFileHandle();
2063 	if (Index == -1)
2064 	{
2065 		if (bToTos)
2066 			return redirect_to_TOS();
2067 
2068 		/* No free handles, return error code */
2069 		Regs[REG_D0] = GEMDOS_ENHNDL;       /* No more handles */
2070 		return true;
2071 	}
2072 
2073 	if ((OverrideHandle = INF_OpenOverride(pszFileName)))
2074 	{
2075 		strcpy(szActualFileName, pszFileName);
2076 		FileHandles[Index].FileHandle = OverrideHandle;
2077 		RealMode = "read-only";
2078 		ModeStr = "rb";
2079 	}
2080 	else
2081 	{
2082 		struct stat FileStat;
2083 		if (bToTos)
2084 			return redirect_to_TOS();
2085 
2086 		/* Convert to hard drive filename */
2087 		GemDOS_CreateHardDriveFileName(Drive, pszFileName,
2088 			szActualFileName, sizeof(szActualFileName));
2089 
2090 		/* Fread/Fwrite calls succeed in all TOS versions
2091 		 * regardless of access rights specified in Fopen().
2092 		 * Only time when things can fail is when file is
2093 		 * opened, if file mode doesn't allow given opening
2094 		 * mode.  As there's no write-only file mode, access
2095 		 * failures happen only when trying to open read-only
2096 		 * file with (read+)write mode.
2097 		 *
2098 		 * Therefore only read-only & read+write modes need
2099 		 * to be supported (ANSI-C fopen() doesn't even
2100 		 * support write-only without truncating the file).
2101 		 *
2102 		 * Read-only status is used if:
2103 		 * - requested by Atari program
2104 		 * - Hatari write protection is enabled
2105 		 * - File itself is read-only
2106 		 * Latter is done to help cases where application
2107 		 * needlessly requests write access, but file is
2108 		 * on read-only media (like CD/DVD).
2109 		 */
2110 		if (Mode == 0 ||
2111 		    ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON ||
2112 		    (stat(szActualFileName, &FileStat) == 0 && !(FileStat.st_mode & S_IWUSR)))
2113 		{
2114 			ModeStr = "rb";
2115 			RealMode = "read-only";
2116 		}
2117 		else
2118 		{
2119 			ModeStr = "rb+";
2120 			RealMode = "read+write";
2121 		}
2122 		FileHandles[Index].FileHandle = fopen(szActualFileName, ModeStr);
2123 	}
2124 
2125 	if (FileHandles[Index].FileHandle != NULL)
2126 	{
2127 		/* Tag handle table entry as used in this process and return handle */
2128 		FileHandles[Index].bUsed = true;
2129 		strcpy(FileHandles[Index].szMode, ModeStr);
2130 		FileHandles[Index].Basepage = STMemory_ReadLong(act_pd);
2131 		snprintf(FileHandles[Index].szActualName,
2132 			 sizeof(FileHandles[Index].szActualName),
2133 			 "%s", szActualFileName);
2134 
2135 		GemDOS_UpdateCurrentProgram(Index);
2136 
2137 		/* Return valid ST file handle from our range (BASE_FILEHANDLE upwards) */
2138 		Regs[REG_D0] = Index+BASE_FILEHANDLE;
2139 		LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> FD %d (%s -> %s)\n",
2140 			  Regs[REG_D0], Modes[Mode], RealMode);
2141 		return true;
2142 	}
2143 
2144 	if (errno == EACCES || errno == EROFS ||
2145 	    errno == EPERM || errno == EISDIR)
2146 	{
2147 		Log_Printf(LOG_WARN, "GEMDOS missing %s permission to file '%s'\n",
2148 			   Modes[Mode], szActualFileName);
2149 		Regs[REG_D0] = GEMDOS_EACCDN;
2150 	}
2151 	else if (errno == ENOTDIR || GemDOS_FilePathMissing(szActualFileName))
2152 	{
2153 		/* Path not found */
2154 		Regs[REG_D0] = GEMDOS_EPTHNF;
2155 	}
2156 	else
2157 	{
2158 		/* File not found / error opening */
2159 		Regs[REG_D0] = GEMDOS_EFILNF;
2160 	}
2161 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "-> ERROR %d (errno = %d)\n", Regs[REG_D0], errno);
2162 	return true;
2163 }
2164 
2165 /*-----------------------------------------------------------------------*/
2166 /**
2167  * GEMDOS Close file
2168  * Call 0x3E
2169  */
GemDOS_Close(Uint32 Params)2170 static bool GemDOS_Close(Uint32 Params)
2171 {
2172 	int i, Handle;
2173 
2174 	/* Find our handle - may belong to TOS */
2175 	Handle = STMemory_ReadWord(Params);
2176 
2177 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE,
2178 		  "GEMDOS 0x3E Fclose(%i) at PC 0x%X\n",
2179 		  Handle, CallingPC);
2180 
2181 	/* Get internal handle */
2182 	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
2183 	{
2184 		/* no, assume it was TOS one -> redirect */
2185 		return false;
2186 	}
2187 
2188 	/* Close file and free up handle table */
2189 	if (INF_CloseOverride(FileHandles[Handle].FileHandle))
2190 	{
2191 		FileHandles[Handle].bUsed = false;
2192 	}
2193 	GemDOS_CloseFileHandle(Handle);
2194 
2195 	/* unalias handle */
2196 	for (i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
2197 	{
2198 		if (ForcedHandles[i].Handle == Handle)
2199 			GemDOS_UnforceFileHandle(i);
2200 	}
2201 	/* Return no error */
2202 	Regs[REG_D0] = GEMDOS_EOK;
2203 	return true;
2204 }
2205 
2206 /*-----------------------------------------------------------------------*/
2207 /**
2208  * GEMDOS Read file
2209  * Call 0x3F
2210  */
GemDOS_Read(Uint32 Params)2211 static bool GemDOS_Read(Uint32 Params)
2212 {
2213 	char *pBuffer;
2214 	off_t CurrentPos, FileSize;
2215 	long nBytesRead, nBytesLeft;
2216 	Uint32 Addr;
2217 	Uint32 Size;
2218 	int Handle;
2219 
2220 	/* Read details from stack */
2221 	Handle = STMemory_ReadWord(Params);
2222 	Size = STMemory_ReadLong(Params+SIZE_WORD);
2223 	Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
2224 
2225 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x3F Fread(%i, %i, 0x%x) at PC 0x%X\n",
2226 	          Handle, Size, Addr,
2227 		  CallingPC);
2228 
2229 	/* Get internal handle */
2230 	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
2231 	{
2232 		/* assume it was TOS one -> redirect */
2233 		return false;
2234 	}
2235 
2236 	/* Old TOS versions treat the Size parameter as signed */
2237 	if (TosVersion < 0x400 && (Size & 0x80000000))
2238 	{
2239 		/* return -1 as original GEMDOS */
2240 		Regs[REG_D0] = -1;
2241 		return true;
2242 	}
2243 
2244 	/* To quick check to see where our file pointer is and how large the file is */
2245 	CurrentPos = ftello(FileHandles[Handle].FileHandle);
2246 	if (CurrentPos == -1L
2247 	    || fseeko(FileHandles[Handle].FileHandle, 0, SEEK_END) != 0)
2248 	{
2249 		Regs[REG_D0] = GEMDOS_E_SEEK;
2250 		return true;
2251 	}
2252 	FileSize = ftello(FileHandles[Handle].FileHandle);
2253 	if (FileSize == -1L
2254 	    || fseeko(FileHandles[Handle].FileHandle, CurrentPos, SEEK_SET) != 0)
2255 	{
2256 		Regs[REG_D0] = GEMDOS_E_SEEK;
2257 		return true;
2258 	}
2259 
2260 	nBytesLeft = FileSize-CurrentPos;
2261 
2262 	/* Check for bad size and End Of File */
2263 	if (Size <= 0 || nBytesLeft <= 0)
2264 	{
2265 		/* return zero (bytes read) as original GEMDOS/EmuTOS */
2266 		Regs[REG_D0] = 0;
2267 		return true;
2268 	}
2269 
2270 	/* Limit to size of file to prevent errors */
2271 	if (Size > (Uint32)nBytesLeft)
2272 		Size = nBytesLeft;
2273 
2274 	/* Check that read is to valid memory area */
2275 	if ( !STMemory_CheckAreaType ( Addr, Size, ABFLAG_RAM ) )
2276 	{
2277 		Log_Printf(LOG_WARN, "GEMDOS Fread() failed due to invalid RAM range 0x%x+%i\n", Addr, Size);
2278 		Regs[REG_D0] = GEMDOS_ERANGE;
2279 		return true;
2280 	}
2281 
2282 	/* Atari memory modified directly with fread() -> flush the instr/data caches */
2283 	M68000_Flush_All_Caches(Addr, Size);
2284 
2285 	/* And read data in */
2286 	pBuffer = (char *)STMemory_STAddrToPointer(Addr);
2287 	nBytesRead = fread(pBuffer, 1, Size, FileHandles[Handle].FileHandle);
2288 
2289 	if (ferror(FileHandles[Handle].FileHandle))
2290 	{
2291 		Log_Printf(LOG_WARN, "GEMDOS failed to read from '%s': %s\n",
2292 			   FileHandles[Handle].szActualName, strerror(errno));
2293 		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
2294 	} else
2295 		/* Return number of bytes read */
2296 		Regs[REG_D0] = nBytesRead;
2297 
2298 	return true;
2299 }
2300 
2301 /*-----------------------------------------------------------------------*/
2302 /**
2303  * GEMDOS Write file
2304  * Call 0x40
2305  */
GemDOS_Write(Uint32 Params)2306 static bool GemDOS_Write(Uint32 Params)
2307 {
2308 	char *pBuffer;
2309 	long nBytesWritten;
2310 	Uint32 Addr;
2311 	Sint32 Size;
2312 	int Handle, fh_idx;
2313 	FILE *fp;
2314 
2315 	/* Read details from stack */
2316 	Handle = STMemory_ReadWord(Params);
2317 	Size = STMemory_ReadLong(Params+SIZE_WORD);
2318 	Addr = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
2319 
2320 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x40 Fwrite(%i, %i, 0x%x) at PC 0x%X\n",
2321 	          Handle, Size, Addr,
2322 		  CallingPC);
2323 
2324 	/* Get internal handle */
2325 	fh_idx = GemDOS_GetValidFileHandle(Handle);
2326 	if (fh_idx >= 0)
2327 	{
2328 		/* write protected device? */
2329 		if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
2330 		{
2331 			Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fwrite(%d,...)\n", Handle);
2332 			Regs[REG_D0] = GEMDOS_EWRPRO;
2333 			return true;
2334 		}
2335 		fp = FileHandles[fh_idx].FileHandle;
2336 	}
2337 	else
2338 	{
2339 		if (!bUseTos && Handle == 1)
2340 			fp = stdout;
2341 		else if (!bUseTos && (Handle == 2 || Handle == -1))
2342 			fp = stderr;
2343 		else
2344 			return false;	/* assume it was TOS one -> redirect */
2345 	}
2346 
2347 	/* Check that write is from valid memory area */
2348 	if (!STMemory_CheckAreaType(Addr, Size, ABFLAG_RAM | ABFLAG_ROM))
2349 	{
2350 		Log_Printf(LOG_WARN, "GEMDOS Fwrite() failed due to invalid RAM range 0x%x+%i\n", Addr, Size);
2351 		Regs[REG_D0] = GEMDOS_ERANGE;
2352 		return true;
2353 	}
2354 
2355 	pBuffer = (char *)STMemory_STAddrToPointer(Addr);
2356 	nBytesWritten = fwrite(pBuffer, 1, Size, fp);
2357 	if (fh_idx >= 0 && ferror(fp))
2358 	{
2359 		Log_Printf(LOG_WARN, "GEMDOS failed to write to '%s'\n",
2360 			   FileHandles[fh_idx].szActualName);
2361 		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
2362 	}
2363 	else
2364 	{
2365 		fflush(fp);
2366 		Regs[REG_D0] = nBytesWritten;      /* OK */
2367 	}
2368 	return true;
2369 }
2370 
2371 
2372 /*-----------------------------------------------------------------------*/
2373 /**
2374  * GEMDOS Delete file
2375  * Call 0x41
2376  */
GemDOS_FDelete(Uint32 Params)2377 static bool GemDOS_FDelete(Uint32 Params)
2378 {
2379 	char *pszFileName, *psActualFileName;
2380 	int Drive;
2381 
2382 	/* Find filename */
2383 	pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2384 
2385 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x41 Fdelete(\"%s\") at PC 0x%X\n", pszFileName,
2386 		  CallingPC);
2387 
2388 	Drive = GemDOS_FileName2HardDriveID(pszFileName);
2389 
2390 	if (!ISHARDDRIVE(Drive))
2391 	{
2392 		/* redirect to TOS */
2393 		return false;
2394 	}
2395 
2396 	/* write protected device? */
2397 	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
2398 	{
2399 		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdelete(\"%s\")\n", pszFileName);
2400 		Regs[REG_D0] = GEMDOS_EWRPRO;
2401 		return true;
2402 	}
2403 
2404 	psActualFileName = malloc(FILENAME_MAX);
2405 	if (!psActualFileName)
2406 	{
2407 		perror("GemDOS_FDelete");
2408 		Regs[REG_D0] = GEMDOS_ENSMEM;
2409 		return true;
2410 	}
2411 
2412 	/* And convert to hard drive filename */
2413 	GemDOS_CreateHardDriveFileName(Drive, pszFileName, psActualFileName, FILENAME_MAX);
2414 
2415 	/* Now delete file?? */
2416 	if (unlink(psActualFileName) == 0)
2417 		Regs[REG_D0] = GEMDOS_EOK;          /* OK */
2418 	else
2419 		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
2420 
2421 	free(psActualFileName);
2422 	return true;
2423 }
2424 
2425 
2426 /*-----------------------------------------------------------------------*/
2427 /**
2428  * GEMDOS File seek
2429  * Call 0x42
2430  */
GemDOS_LSeek(Uint32 Params)2431 static bool GemDOS_LSeek(Uint32 Params)
2432 {
2433 	long Offset;
2434 	int Handle, Mode;
2435 	long nFileSize;
2436 	long nOldPos, nDestPos;
2437 	FILE *fhndl;
2438 
2439 	/* Read details from stack */
2440 	Offset = (Sint32)STMemory_ReadLong(Params);
2441 	Handle = STMemory_ReadWord(Params+SIZE_LONG);
2442 	Mode = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);
2443 
2444 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x42 Fseek(%li, %i, %i) at PC 0x%X\n", Offset, Handle, Mode,
2445 		  CallingPC);
2446 
2447 	/* get internal handle */
2448 	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
2449 	{
2450 		/* assume it was TOS one -> redirect */
2451 		return false;
2452 	}
2453 
2454 	fhndl = FileHandles[Handle].FileHandle;
2455 
2456 	/* Save old position in file */
2457 	nOldPos = ftell(fhndl);
2458 
2459 	/* Determine the size of the file */
2460 	if (fseek(fhndl, 0L, SEEK_END) != 0 || nOldPos < 0)
2461 	{
2462 		Regs[REG_D0] = GEMDOS_E_SEEK;
2463 		return true;
2464 	}
2465 	nFileSize = ftell(fhndl);
2466 
2467 	switch (Mode)
2468 	{
2469 	 case 0: nDestPos = Offset; break; /* positive offset */
2470 	 case 1: nDestPos = nOldPos + Offset; break;
2471 	 case 2: nDestPos = nFileSize + Offset; break; /* negative offset */
2472 	 default: nDestPos = -1;
2473 	}
2474 
2475 	if (nDestPos < 0 || nDestPos > nFileSize)
2476 	{
2477 		/* Restore old position and return error */
2478 		if (fseek(fhndl, nOldPos, SEEK_SET) != 0)
2479 			perror("GemDOS_LSeek");
2480 		Regs[REG_D0] = GEMDOS_ERANGE;
2481 		return true;
2482 	}
2483 
2484 	/* Seek to new position and return offset from start of file */
2485 	if (fseek(fhndl, nDestPos, SEEK_SET) != 0)
2486 		perror("GemDOS_LSeek");
2487 	Regs[REG_D0] = ftell(fhndl);
2488 
2489 	return true;
2490 }
2491 
2492 
2493 /*-----------------------------------------------------------------------*/
2494 /**
2495  * GEMDOS Fattrib() - get or set file and directory attributes
2496  * Call 0x43
2497  */
GemDOS_Fattrib(Uint32 Params)2498 static bool GemDOS_Fattrib(Uint32 Params)
2499 {
2500 	/* TODO: host filenames might not fit into this */
2501 	char sActualFileName[MAX_GEMDOS_PATH];
2502 	char *psFileName;
2503 	int nDrive;
2504 	int nRwFlag, nAttrib;
2505 	struct stat FileStat;
2506 
2507 	/* Find filename */
2508 	psFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2509 	nDrive = GemDOS_FileName2HardDriveID(psFileName);
2510 
2511 	nRwFlag = STMemory_ReadWord(Params+SIZE_LONG);
2512 	nAttrib = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);
2513 
2514 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x43 Fattrib(\"%s\", %d, 0x%x) at PC 0x%X\n",
2515 	          psFileName, nRwFlag, nAttrib,
2516 		  CallingPC);
2517 
2518 	if (!ISHARDDRIVE(nDrive))
2519 	{
2520 		/* redirect to TOS */
2521 		return false;
2522 	}
2523 
2524 	/* Convert to hard drive filename */
2525 	GemDOS_CreateHardDriveFileName(nDrive, psFileName,
2526 	                              sActualFileName, sizeof(sActualFileName));
2527 
2528 	if (nAttrib == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
2529 	{
2530 		Log_Printf(LOG_WARN, "Hatari doesn't support GEMDOS volume label setting\n(for '%s')\n", sActualFileName);
2531 		Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
2532 		return true;
2533 	}
2534 	if (stat(sActualFileName, &FileStat) != 0)
2535 	{
2536 		Regs[REG_D0] = GEMDOS_EFILNF;         /* File not found */
2537 		return true;
2538 	}
2539 	if (nRwFlag == 0)
2540 	{
2541 		/* Read attributes */
2542 		Regs[REG_D0] = GemDOS_ConvertAttribute(FileStat.st_mode);
2543 		return true;
2544 	}
2545 
2546 	/* prevent modifying access rights both on write & auto-protected devices */
2547 	if (ConfigureParams.HardDisk.nWriteProtection != WRITEPROT_OFF)
2548 	{
2549 		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fattrib(\"%s\",...)\n", psFileName);
2550 		Regs[REG_D0] = GEMDOS_EWRPRO;
2551 		return true;
2552 	}
2553 
2554 	if (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY)
2555 	{
2556 		if (!S_ISDIR(FileStat.st_mode))
2557 		{
2558 			/* file, not dir -> path not found */
2559 			Regs[REG_D0] = GEMDOS_EPTHNF;
2560 			return true;
2561 		}
2562 	}
2563 	else
2564 	{
2565 		if (S_ISDIR(FileStat.st_mode))
2566 		{
2567 			/* dir, not file -> file not found */
2568 			Regs[REG_D0] = GEMDOS_EFILNF;
2569 			return true;
2570 		}
2571 	}
2572 
2573 	if (nAttrib & GEMDOS_FILE_ATTRIB_READONLY)
2574 	{
2575 		/* set read-only (readable by all) */
2576 		if (chmod(sActualFileName, S_IRUSR|S_IRGRP|S_IROTH) == 0)
2577 		{
2578 			Regs[REG_D0] = nAttrib;
2579 			return true;
2580 		}
2581 	}
2582 	else
2583 	{
2584 		/* set writable (by user, readable by all) */
2585 		if (chmod(sActualFileName, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH) == 0)
2586 		{
2587 			Regs[REG_D0] = nAttrib;
2588 			return true;
2589 		}
2590 	}
2591 
2592 	/* FIXME: support hidden/system/archive flags?
2593 	 * System flag is from DOS, not used by TOS.
2594 	 * Archive bit is cleared by backup programs
2595 	 * and set whenever file is written to.
2596 	 */
2597 
2598 	Regs[REG_D0] = errno2gemdos(errno, (nAttrib & GEMDOS_FILE_ATTRIB_SUBDIRECTORY) ? ERROR_PATH : ERROR_FILE);
2599 	return true;
2600 }
2601 
2602 
2603 /*-----------------------------------------------------------------------*/
2604 /**
2605  * GEMDOS Force (file handle aliasing)
2606  * Call 0x46
2607  */
GemDOS_Force(Uint32 Params)2608 static bool GemDOS_Force(Uint32 Params)
2609 {
2610 	int std, own;
2611 
2612 	/* Read details from stack */
2613 	std = STMemory_ReadWord(Params);
2614         own = STMemory_ReadWord(Params+SIZE_WORD);
2615 
2616 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x46 Fforce(%d, %d) at PC 0x%X\n", std, own,
2617 		  CallingPC);
2618 
2619 	/* Get internal handle */
2620 	if (std > own)
2621 	{
2622 		int tmp = std;
2623 		std = own;
2624 		own = tmp;
2625 	}
2626 	if ((own = GemDOS_GetValidFileHandle(own)) < 0)
2627 	{
2628 		/* assume it was TOS one -> let TOS handle it */
2629 		return false;
2630 	}
2631 	if (std < 0 || std >= ARRAY_SIZE(ForcedHandles))
2632 	{
2633 		Log_Printf(LOG_WARN, "forcing of non-standard %d (> %d) handle ignored.\n", std, ARRAY_SIZE(ForcedHandles));
2634 		return false;
2635 	}
2636 	/* mark given standard handle redirected by this process */
2637 	ForcedHandles[std].Basepage = STMemory_ReadLong(act_pd);
2638 	ForcedHandles[std].Handle = own;
2639 
2640 	Regs[REG_D0] = GEMDOS_EOK;
2641 	return true;
2642 }
2643 
2644 
2645 /*-----------------------------------------------------------------------*/
2646 /**
2647  * GEMDOS Get Directory
2648  * Call 0x47
2649  */
GemDOS_GetDir(Uint32 Params)2650 static bool GemDOS_GetDir(Uint32 Params)
2651 {
2652 	Uint32 Address;
2653 	Uint16 Drive;
2654 
2655 	Address = STMemory_ReadLong(Params);
2656 	Drive = STMemory_ReadWord(Params+SIZE_LONG);
2657 
2658 	/* Note: Drive = 0 means current drive, 1 = A:, 2 = B:, 3 = C:, etc. */
2659 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x47 Dgetpath(0x%x, %i) at PC 0x%X\n", Address, (int)Drive,
2660 		  CallingPC);
2661 	if (Drive == 0)
2662 		Drive = CurrentDrive;
2663 	else
2664 		Drive--;
2665 
2666 	/* is it our drive? */
2667 	if (GemDOS_IsDriveEmulated(Drive))
2668 	{
2669 		char path[MAX_GEMDOS_PATH];
2670 		int i,len,c;
2671 
2672 		*path = '\0';
2673 		strncat(path,&emudrives[Drive-2]->fs_currpath[strlen(emudrives[Drive-2]->hd_emulation_dir)], sizeof(path)-1);
2674 
2675 		// convert it to ST path (DOS)
2676 		File_CleanFileName(path);
2677 		len = strlen(path);
2678 		/* Check that write is requested to valid memory area */
2679 		if ( !STMemory_CheckAreaType ( Address, len, ABFLAG_RAM ) )
2680 		{
2681 			Log_Printf(LOG_WARN, "GEMDOS Dgetpath() failed due to invalid RAM range 0x%x+%i\n", Address, len);
2682 			Regs[REG_D0] = GEMDOS_ERANGE;
2683 			return true;
2684 		}
2685 		for (i = 0; i <= len; i++)
2686 		{
2687 			c = path[i];
2688 			STMemory_WriteByte(Address+i, (c==PATHSEP ? '\\' : c) );
2689 		}
2690 		LOG_TRACE(TRACE_OS_GEMDOS, "-> '%s'\n", (char *)STMemory_STAddrToPointer(Address));
2691 
2692 		Regs[REG_D0] = GEMDOS_EOK;          /* OK */
2693 
2694 		return true;
2695 	}
2696 	/* redirect to TOS */
2697 	return false;
2698 }
2699 
2700 
2701 /*-----------------------------------------------------------------------*/
2702 /**
2703  * GEMDOS PExec handler
2704  * Call 0x4B
2705  */
GemDOS_Pexec(Uint32 Params)2706 static int GemDOS_Pexec(Uint32 Params)
2707 {
2708 	int Drive;
2709 	Uint16 Mode;
2710 	char *pszFileName;
2711 
2712 	/* Find PExec mode */
2713 	Mode = STMemory_ReadWord(Params);
2714 
2715 	if (LOG_TRACE_LEVEL(TRACE_OS_GEMDOS|TRACE_OS_BASE))
2716 	{
2717 		Uint32 fname, cmdline, env_string;
2718 		fname = STMemory_ReadLong(Params+SIZE_WORD);
2719 		cmdline = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG);
2720 		env_string = STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG+SIZE_LONG);
2721 		if (Mode == 0 || Mode == 3)
2722 		{
2723 			int cmdlen;
2724 			char *str;
2725 			const char *name, *cmd;
2726 			name = (const char *)STMemory_STAddrToPointer(fname);
2727 			cmd = (const char *)STMemory_STAddrToPointer(cmdline);
2728 			cmdlen = *cmd++;
2729 			str = malloc(cmdlen+1);
2730 			memcpy(str, cmd, cmdlen);
2731 			str[cmdlen] = '\0';
2732 			LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, \"%s\", [%d]\"%s\", 0x%x) at PC 0x%X\n", Mode, name, cmdlen, str, env_string,
2733 				CallingPC);
2734 			free(str);
2735 		}
2736 		else
2737 		{
2738 			LOG_TRACE_PRINT ( "GEMDOS 0x4B Pexec(%i, 0x%x, 0x%x, 0x%x) at PC 0x%X\n", Mode, fname, cmdline, env_string,
2739 				CallingPC);
2740 		}
2741 	}
2742 
2743 	/* Re-direct as needed */
2744 	switch(Mode)
2745 	{
2746 	 case 0:      /* Load and go */
2747 	 case 3:      /* Load, don't go */
2748 		pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD));
2749 		Drive = GemDOS_FileName2HardDriveID(pszFileName);
2750 
2751 		/* If not using A: or B:, use my own routines to load */
2752 		if (ISHARDDRIVE(Drive))
2753 		{
2754 			/* Redirect to cart' routine at address 0xFA1000 */
2755 			PexecCalled = true;
2756 			return CALL_PEXEC_ROUTINE;
2757 		}
2758 		return false;
2759 	 case 4:      /* Just go */
2760 		return false;
2761 	 case 5:      /* Create basepage */
2762 		return false;
2763 	 case 6:
2764 		return false;
2765 	}
2766 
2767 	/* Default: Still re-direct to TOS */
2768 	return false;
2769 }
2770 
2771 
2772 /*-----------------------------------------------------------------------*/
2773 /**
2774  * GEMDOS Search Next
2775  * Call 0x4F
2776  */
GemDOS_SNext(void)2777 static bool GemDOS_SNext(void)
2778 {
2779 	struct dirent **temp;
2780 	int Index;
2781 	int ret;
2782 	DTA *pDTA;
2783 	Uint32 DTA_Gemdos;
2784 
2785 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4F Fsnext() at PC 0x%X\n" , CallingPC);
2786 
2787 	/* Refresh pDTA pointer (from the current basepage) */
2788 	DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd) + BASEPAGE_OFFSET_DTA);
2789 
2790 	if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
2791 	{
2792 		Log_Printf(LOG_WARN, "GEMDOS Fsnext() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
2793 		Regs[REG_D0] = GEMDOS_EINTRN;    /* "internal error */
2794 		return true;
2795 	}
2796 	pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
2797 
2798 	/* Was DTA ours or TOS? */
2799 	if (do_get_mem_long(pDTA->magic) != DTA_MAGIC_NUMBER)
2800 	{
2801 		/* redirect to TOS */
2802 		return false;
2803 	}
2804 
2805 	/* Find index into our list of structures */
2806 	Index = do_get_mem_word(pDTA->index) & MAX_DTAS_MASK;
2807 
2808 	if (nAttrSFirst == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
2809 	{
2810 		/* Volume label was given already in Sfirst() */
2811 		Regs[REG_D0] = GEMDOS_ENMFIL;
2812 		return true;
2813 	}
2814 	if (!InternalDTAs[Index].bUsed)
2815 	{
2816 		/* Invalid handle, TOS returns ENMFIL
2817 		 * (if Fsetdta() has been used by any process)
2818 		 */
2819 		Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Invalid DTA\n");
2820 		Regs[REG_D0] = GEMDOS_ENMFIL;
2821 	}
2822 
2823 	temp = InternalDTAs[Index].found;
2824 	do
2825 	{
2826 		if (InternalDTAs[Index].centry >= InternalDTAs[Index].nentries)
2827 		{
2828 			/* older TOS versions zero file name if there are no (further) matches */
2829 			if (TosVersion < 0x0400)
2830 				pDTA->dta_name[0] = 0;
2831 			Regs[REG_D0] = GEMDOS_ENMFIL;    /* No more files */
2832 			return true;
2833 		}
2834 
2835 		ret = PopulateDTA(InternalDTAs[Index].path,
2836 				  temp[InternalDTAs[Index].centry++],
2837 				  pDTA, DTA_Gemdos);
2838 	} while (ret == 1);
2839 
2840 	if (ret < 0)
2841 	{
2842 		Log_Printf(LOG_WARN, "GEMDOS Fsnext(): Error setting DTA\n");
2843 		Regs[REG_D0] = GEMDOS_EINTRN;
2844 		return true;
2845 	}
2846 
2847 	Regs[REG_D0] = GEMDOS_EOK;
2848 	return true;
2849 }
2850 
2851 
2852 /*-----------------------------------------------------------------------*/
2853 /**
2854  * GEMDOS Find first file
2855  * Call 0x4E
2856  */
GemDOS_SFirst(Uint32 Params)2857 static bool GemDOS_SFirst(Uint32 Params)
2858 {
2859 	/* TODO: host filenames might not fit into this */
2860 	char szActualFileName[MAX_GEMDOS_PATH];
2861 	char *pszFileName;
2862 	const char *dirmask;
2863 	struct dirent **files;
2864 	int Drive;
2865 	DIR *fsdir;
2866 	int i,j,count;
2867 	DTA *pDTA;
2868 	Uint32 DTA_Gemdos;
2869 
2870 	/* Find filename to search for */
2871 	pszFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params));
2872 	nAttrSFirst = STMemory_ReadWord(Params+SIZE_LONG);
2873 
2874 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4E Fsfirst(\"%s\", 0x%x) at PC 0x%X\n", pszFileName, nAttrSFirst,
2875 		  CallingPC);
2876 
2877 	Drive = GemDOS_FileName2HardDriveID(pszFileName);
2878 	if (!ISHARDDRIVE(Drive))
2879 	{
2880 		/* redirect to TOS */
2881 		return false;
2882 	}
2883 
2884 	/* Convert to hard drive filename */
2885 	GemDOS_CreateHardDriveFileName(Drive, pszFileName,
2886 		                    szActualFileName, sizeof(szActualFileName));
2887 
2888 	/* Refresh pDTA pointer (from the current basepage) */
2889 	DTA_Gemdos = STMemory_ReadLong(STMemory_ReadLong(act_pd) + BASEPAGE_OFFSET_DTA);
2890 
2891 	if ( !STMemory_CheckAreaType ( DTA_Gemdos, sizeof(DTA), ABFLAG_RAM ) )
2892 	{
2893 		Log_Printf(LOG_WARN, "GEMDOS Fsfirst() failed due to invalid DTA address 0x%x\n", DTA_Gemdos);
2894 		Regs[REG_D0] = GEMDOS_EINTRN;    /* "internal error */
2895 		return true;
2896 	}
2897 
2898 	/* Atari memory modified directly with do_mem_* + strcpy() -> flush the data cache */
2899 	M68000_Flush_Data_Cache(DTA_Gemdos, sizeof(DTA));
2900 
2901 	pDTA = (DTA *)STMemory_STAddrToPointer(DTA_Gemdos);
2902 
2903 	/* Populate DTA, set index for our use */
2904 	do_put_mem_word(pDTA->index, DTAIndex);
2905 	/* set our dta magic num */
2906 	do_put_mem_long(pDTA->magic, DTA_MAGIC_NUMBER);
2907 
2908 	if (InternalDTAs[DTAIndex].bUsed == true)
2909 		ClearInternalDTA(DTAIndex);
2910 	InternalDTAs[DTAIndex].bUsed = true;
2911 
2912 	/* Were we looking for the volume label? */
2913 	if (nAttrSFirst == GEMDOS_FILE_ATTRIB_VOLUME_LABEL)
2914 	{
2915 		/* Volume name */
2916 		strcpy(pDTA->dta_name,"EMULATED.001");
2917 		pDTA->dta_name[11] = '0' + Drive;
2918 		Regs[REG_D0] = GEMDOS_EOK;          /* Got volume */
2919 		return true;
2920 	}
2921 
2922 	/* open directory
2923 	 * TODO: host path may not fit into InternalDTA
2924 	 */
2925 	fsfirst_dirname(szActualFileName, InternalDTAs[DTAIndex].path);
2926 	fsdir = opendir(InternalDTAs[DTAIndex].path);
2927 
2928 	if (fsdir == NULL)
2929 	{
2930 		Regs[REG_D0] = GEMDOS_EPTHNF;        /* Path not found */
2931 		return true;
2932 	}
2933 	/* close directory */
2934 	closedir(fsdir);
2935 
2936 	count = scandir(InternalDTAs[DTAIndex].path, &files, 0, alphasort);
2937 	/* File (directory actually) not found */
2938 	if (count < 0)
2939 	{
2940 		Regs[REG_D0] = GEMDOS_EFILNF;
2941 		return true;
2942 	}
2943 
2944 	InternalDTAs[DTAIndex].centry = 0;          /* current entry is 0 */
2945 	dirmask = fsfirst_dirmask(szActualFileName);/* directory mask part */
2946 	InternalDTAs[DTAIndex].found = files;       /* get files */
2947 
2948 	/* count & copy the entries that match our mask and discard the rest */
2949 	j = 0;
2950 	for (i=0; i < count; i++)
2951 	{
2952 		Str_DecomposedToPrecomposedUtf8(files[i]->d_name, files[i]->d_name);   /* for OSX */
2953 		if (fsfirst_match(dirmask, files[i]->d_name))
2954 		{
2955 			InternalDTAs[DTAIndex].found[j] = files[i];
2956 			j++;
2957 		}
2958 		else
2959 		{
2960 			free(files[i]);
2961 			files[i] = NULL;
2962 		}
2963 	}
2964 	InternalDTAs[DTAIndex].nentries = j; /* set number of legal entries */
2965 
2966 	/* No files of that match, return error code */
2967 	if (j==0)
2968 	{
2969 		free(files);
2970 		InternalDTAs[DTAIndex].found = NULL;
2971 		Regs[REG_D0] = GEMDOS_EFILNF;        /* File not found */
2972 		return true;
2973 	}
2974 
2975 	/* Scan for first file (SNext uses no parameters) */
2976 	GemDOS_SNext();
2977 	/* increment DTA index */
2978 	DTAIndex++;
2979 	DTAIndex &= MAX_DTAS_MASK;
2980 
2981 	return true;
2982 }
2983 
2984 
2985 /*-----------------------------------------------------------------------*/
2986 /**
2987  * GEMDOS Rename
2988  * Call 0x56
2989  */
GemDOS_Rename(Uint32 Params)2990 static bool GemDOS_Rename(Uint32 Params)
2991 {
2992 	char *pszNewFileName,*pszOldFileName;
2993 	/* TODO: host filenames might not fit into this */
2994 	char szNewActualFileName[MAX_GEMDOS_PATH];
2995 	char szOldActualFileName[MAX_GEMDOS_PATH];
2996 	int NewDrive, OldDrive;
2997 
2998 	/* Read details from stack, skip first (dummy) arg */
2999 	pszOldFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD));
3000 	pszNewFileName = (char *)STMemory_STAddrToPointer(STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG));
3001 
3002 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x56 Frename(\"%s\", \"%s\") at PC 0x%X\n", pszOldFileName, pszNewFileName,
3003 		  CallingPC);
3004 
3005 	NewDrive = GemDOS_FileName2HardDriveID(pszNewFileName);
3006 	OldDrive = GemDOS_FileName2HardDriveID(pszOldFileName);
3007 	if (!(ISHARDDRIVE(NewDrive) && ISHARDDRIVE(OldDrive)))
3008 	{
3009 		/* redirect to TOS */
3010 		return false;
3011 	}
3012 
3013 	/* write protected device? */
3014 	if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
3015 	{
3016 		Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Frename(\"%s\", \"%s\")\n", pszOldFileName, pszNewFileName);
3017 		Regs[REG_D0] = GEMDOS_EWRPRO;
3018 		return true;
3019 	}
3020 
3021 	/* And convert to hard drive filenames */
3022 	GemDOS_CreateHardDriveFileName(NewDrive, pszNewFileName,
3023 		              szNewActualFileName, sizeof(szNewActualFileName));
3024 	GemDOS_CreateHardDriveFileName(OldDrive, pszOldFileName,
3025 		              szOldActualFileName, sizeof(szOldActualFileName));
3026 
3027 	/* Rename files */
3028 	if (rename(szOldActualFileName,szNewActualFileName) == 0)
3029 		Regs[REG_D0] = GEMDOS_EOK;
3030 	else
3031 		Regs[REG_D0] = errno2gemdos(errno, ERROR_FILE);
3032 	return true;
3033 }
3034 
3035 
3036 /*-----------------------------------------------------------------------*/
3037 /**
3038  * GEMDOS GSDToF
3039  * Call 0x57
3040  */
GemDOS_GSDToF(Uint32 Params)3041 static bool GemDOS_GSDToF(Uint32 Params)
3042 {
3043 	DATETIME DateTime;
3044 	Uint32 pBuffer;
3045 	int Handle,Flag;
3046 
3047 	/* Read details from stack */
3048 	pBuffer = STMemory_ReadLong(Params);
3049 	Handle = STMemory_ReadWord(Params+SIZE_LONG);
3050 	Flag = STMemory_ReadWord(Params+SIZE_LONG+SIZE_WORD);
3051 
3052 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x57 Fdatime(0x%x, %i, %i) at PC 0x%X\n", pBuffer,
3053 	          Handle, Flag,
3054 		  CallingPC);
3055 
3056 	/* get internal handle */
3057 	if ((Handle = GemDOS_GetValidFileHandle(Handle)) < 0)
3058 	{
3059 		/* No, assume was TOS -> redirect */
3060 		return false;
3061 	}
3062 
3063 	if (Flag == 1)
3064 	{
3065 		/* write protected device? */
3066 		if (ConfigureParams.HardDisk.nWriteProtection == WRITEPROT_ON)
3067 		{
3068 			Log_Printf(LOG_WARN, "PREVENTED: GEMDOS Fdatime(,%d,)\n", Handle);
3069 			Regs[REG_D0] = GEMDOS_EWRPRO;
3070 			return true;
3071 		}
3072 		DateTime.timeword = STMemory_ReadWord(pBuffer);
3073 		DateTime.dateword = STMemory_ReadWord(pBuffer+SIZE_WORD);
3074 		if (GemDOS_SetFileInformation(Handle, &DateTime) == true)
3075 			Regs[REG_D0] = GEMDOS_EOK;
3076 		else
3077 			Regs[REG_D0] = GEMDOS_EACCDN;        /* Access denied */
3078 		return true;
3079 	}
3080 
3081 	if (GemDOS_GetFileInformation(Handle, &DateTime) == true)
3082 	{
3083 		/* Check that write is requested to valid memory area */
3084 		if ( STMemory_CheckAreaType ( pBuffer, 4, ABFLAG_RAM ) )
3085 		{
3086 			STMemory_WriteWord(pBuffer, DateTime.timeword);
3087 			STMemory_WriteWord(pBuffer+SIZE_WORD, DateTime.dateword);
3088 			Regs[REG_D0] = GEMDOS_EOK;
3089 		}
3090 		else
3091 		{
3092 			Log_Printf(LOG_WARN, "GEMDOS Fdatime() failed due to invalid RAM range 0x%x+%i\n", pBuffer, 4);
3093 			Regs[REG_D0] = GEMDOS_ERANGE;
3094 		}
3095 	}
3096 	else
3097 	{
3098 		Regs[REG_D0] = GEMDOS_ERROR; /* Generic error */
3099 	}
3100 	return true;
3101 }
3102 
3103 
3104 /*-----------------------------------------------------------------------*/
3105 /**
3106  * Do implicit file handle closing/unforcing on program termination
3107  */
GemDOS_TerminateClose(void)3108 static void GemDOS_TerminateClose(void)
3109 {
3110 	int i, closed, unforced;
3111 	Uint32 current = STMemory_ReadLong(act_pd);
3112 
3113 	closed = 0;
3114 	for (i = 0; i < ARRAY_SIZE(FileHandles); i++)
3115 	{
3116 		if (FileHandles[i].Basepage == current)
3117 		{
3118 			GemDOS_CloseFileHandle(i);
3119 			closed++;
3120 		}
3121 	}
3122 	unforced = 0;
3123 	for (i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
3124 	{
3125 		if (ForcedHandles[i].Basepage == current)
3126 		{
3127 			GemDOS_UnforceFileHandle(i);
3128 			unforced++;
3129 		}
3130 	}
3131 	if (!(closed || unforced))
3132 		return;
3133 	Log_Printf(LOG_WARN, "Closing %d & unforcing %d file handle(s) remaining at program 0x%x exit.\n",
3134 		   closed, unforced, current);
3135 }
3136 
3137 /**
3138  * GEMDOS Pterm0
3139  * Call 0x00
3140  */
GemDOS_Pterm0(Uint32 Params)3141 static bool GemDOS_Pterm0(Uint32 Params)
3142 {
3143 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x00 Pterm0() at PC 0x%X\n",
3144 		  CallingPC);
3145 	GemDOS_TerminateClose();
3146 	Symbols_RemoveCurrentProgram();
3147 
3148 	if (!bUseTos)
3149 	{
3150 		Main_SetQuitValue(0);
3151 		return true;
3152 	}
3153 
3154 	return false;
3155 }
3156 
3157 /**
3158  * GEMDOS Ptermres
3159  * Call 0x31
3160  */
GemDOS_Ptermres(Uint32 Params)3161 static bool GemDOS_Ptermres(Uint32 Params)
3162 {
3163 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x31 Ptermres(0x%X, %hd) at PC 0x%X\n",
3164 		  STMemory_ReadLong(Params), (Sint16)STMemory_ReadWord(Params+SIZE_WORD),
3165 		  CallingPC);
3166 	GemDOS_TerminateClose();
3167 	return false;
3168 }
3169 
3170 /**
3171  * GEMDOS Pterm
3172  * Call 0x4c
3173  */
GemDOS_Pterm(Uint32 Params)3174 static bool GemDOS_Pterm(Uint32 Params)
3175 {
3176 	uint16_t nExitVal = STMemory_ReadWord(Params);
3177 
3178 	LOG_TRACE(TRACE_OS_GEMDOS|TRACE_OS_BASE, "GEMDOS 0x4C Pterm(%hd) at PC 0x%X\n",
3179 		  nExitVal, CallingPC);
3180 
3181 	GemDOS_TerminateClose();
3182 	Symbols_RemoveCurrentProgram();
3183 
3184 	if (!bUseTos)
3185 	{
3186 		Main_SetQuitValue(nExitVal);
3187 		return true;
3188 	}
3189 
3190 	return false;
3191 }
3192 
3193 /**
3194  * GEMDOS Super
3195  * Call 0x20
3196  */
GemDOS_Super(Uint32 Params)3197 static bool GemDOS_Super(Uint32 Params)
3198 {
3199 	uint32_t nParam = STMemory_ReadLong(Params);
3200 	uint32_t nExcFrameSize, nRetAddr;
3201 	uint16_t nSR, nVec = 0;
3202 
3203 	LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x20 Super(0x%X) at PC 0x%X\n",
3204 		  nParam, CallingPC);
3205 
3206 	/* This call is normally fully handled by TOS - we only
3207 	 * need to emulate it for TOS-less testing mode */
3208 	if (bUseTos)
3209 		return false;
3210 
3211 	/* Get SR, return address and vector offset from stack frame */
3212 	nSR = STMemory_ReadWord(Regs[REG_A7]);
3213 	nRetAddr = STMemory_ReadLong(Regs[REG_A7] + SIZE_WORD);
3214 	if (currprefs.cpu_level > 0)
3215 		nVec = STMemory_ReadWord(Regs[REG_A7] + SIZE_WORD + SIZE_LONG);
3216 
3217 	if (nParam == 1)                /* Query mode? */
3218 	{
3219 		Regs[REG_D0] = (nSR & SR_SUPERMODE) ? -1 : 0;
3220 		return true;
3221 	}
3222 
3223 	if (nParam == 0)
3224 	{
3225 		nParam = regs.usp;
3226 	}
3227 
3228 	if (currprefs.cpu_level > 0)
3229 		nExcFrameSize = SIZE_WORD + SIZE_LONG + SIZE_WORD;
3230 	else
3231 		nExcFrameSize = SIZE_WORD + SIZE_LONG;
3232 
3233 
3234 	Regs[REG_D0] = Regs[REG_A7] + nExcFrameSize;
3235 	Regs[REG_A7] = nParam - nExcFrameSize;
3236 
3237 	nSR ^= SR_SUPERMODE;
3238 
3239 	STMemory_WriteWord(Regs[REG_A7], nSR);
3240 	STMemory_WriteLong(Regs[REG_A7] + SIZE_WORD, nRetAddr);
3241 	STMemory_WriteWord(Regs[REG_A7] + SIZE_WORD + SIZE_LONG, nVec);
3242 
3243 	return true;
3244 }
3245 
3246 
3247 /**
3248  * Map GEMDOS call opcodes to their names
3249  *
3250  * Mapping is based on TOSHYP information:
3251  *	http://toshyp.atari.org/en/005013.html
3252  */
GemDOS_Opcode2Name(Uint16 opcode)3253 static const char* GemDOS_Opcode2Name(Uint16 opcode)
3254 {
3255 	static const char* names[] = {
3256 		"Pterm0",
3257 		"Cconin",
3258 		"Cconout",
3259 		"Cauxin",
3260 		"Cauxout",
3261 		"Cprnout",
3262 		"Crawio",
3263 		"Crawcin",
3264 		"Cnecin",
3265 		"Cconws",
3266 		"Cconrs",
3267 		"Cconis",
3268 		"-", /* 0C */
3269 		"-", /* 0D */
3270 		"Dsetdrv",
3271 		"-", /* 0F */
3272 		"Cconos",
3273 		"Cprnos",
3274 		"Cauxis",
3275 		"Cauxos",
3276 		"Maddalt",
3277 		"Srealloc", /* TOS4 */
3278 		"-", /* 16 */
3279 		"-", /* 17 */
3280 		"-", /* 18 */
3281 		"Dgetdrv",
3282 		"Fsetdta",
3283 		"-", /* 1B */
3284 		"-", /* 1C */
3285 		"-", /* 1D */
3286 		"-", /* 1E */
3287 		"-", /* 1F */
3288 		"Super",
3289 		"-", /* 21 */
3290 		"-", /* 22 */
3291 		"-", /* 23 */
3292 		"-", /* 24 */
3293 		"-", /* 25 */
3294 		"-", /* 26 */
3295 		"-", /* 27 */
3296 		"-", /* 28 */
3297 		"-", /* 29 */
3298 		"Tgetdate",
3299 		"Tsetdate",
3300 		"Tgettime",
3301 		"Tsettime",
3302 		"-", /* 2E */
3303 		"Fgetdta",
3304 		"Sversion",
3305 		"Ptermres",
3306 		"-", /* 32 */
3307 		"-", /* 33 */
3308 		"-", /* 34 */
3309 		"-", /* 35 */
3310 		"Dfree",
3311 		"-", /* 37 */
3312 		"-", /* 38 */
3313 		"Dcreate",
3314 		"Ddelete",
3315 		"Dsetpath",
3316 		"Fcreate",
3317 		"Fopen",
3318 		"Fclose",
3319 		"Fread",
3320 		"Fwrite",
3321 		"Fdelete",
3322 		"Fseek",
3323 		"Fattrib",
3324 		"Mxalloc",
3325 		"Fdup",
3326 		"Fforce",
3327 		"Dgetpath",
3328 		"Malloc",
3329 		"Mfree",
3330 		"Mshrink",
3331 		"Pexec",
3332 		"Pterm",
3333 		"-", /* 4D */
3334 		"Fsfirst",
3335 		"Fsnext",
3336 		"-", /* 50 */
3337 		"-", /* 51 */
3338 		"-", /* 52 */
3339 		"-", /* 53 */
3340 		"-", /* 54 */
3341 		"-", /* 55 */
3342 		"Frename",
3343 		"Fdatime",
3344 		"-", /* 58 */
3345 		"-", /* 59 */
3346 		"-", /* 5A */
3347 		"-", /* 5B */
3348 		"Flock", /* 5C */
3349 		"-", /* 5D */
3350 		"-", /* 5E */
3351 		"-", /* 5F */
3352 		"Nversion", /* 60 */
3353 		"-", /* 61 */
3354 		"-", /* 62 */
3355 		"-", /* 63 */
3356 		"-", /* 64 */
3357 		"-", /* 65 */
3358 		"-", /* 66 */
3359 		"-", /* 67 */
3360 		"-", /* 68 */
3361 		"-", /* 69 */
3362 		"-", /* 6A */
3363 		"-", /* 6B */
3364 		"-", /* 6C */
3365 		"-", /* 6D */
3366 		"-", /* 6E */
3367 		"-", /* 6F */
3368 		"-", /* 70 */
3369 		"-", /* 71 */
3370 		"-", /* 72 */
3371 		"-", /* 73 */
3372 		"-", /* 74 */
3373 		"-", /* 75 */
3374 		"-", /* 76 */
3375 		"-", /* 77 */
3376 		"-", /* 78 */
3377 		"-", /* 79 */
3378 		"-", /* 7A */
3379 		"-", /* 7B */
3380 		"-", /* 7C */
3381 		"-", /* 7D */
3382 		"-", /* 7E */
3383 		"-", /* 7F */
3384 		"-", /* 80 */
3385 		"-", /* 81 */
3386 		"-", /* 82 */
3387 		"-", /* 83 */
3388 		"-", /* 84 */
3389 		"-", /* 85 */
3390 		"-", /* 86 */
3391 		"-", /* 87 */
3392 		"-", /* 88 */
3393 		"-", /* 89 */
3394 		"-", /* 8A */
3395 		"-", /* 8B */
3396 		"-", /* 8C */
3397 		"-", /* 8D */
3398 		"-", /* 8E */
3399 		"-", /* 8F */
3400 		"-", /* 90 */
3401 		"-", /* 91 */
3402 		"-", /* 92 */
3403 		"-", /* 93 */
3404 		"-", /* 94 */
3405 		"-", /* 95 */
3406 		"-", /* 96 */
3407 		"-", /* 97 */
3408 		"-", /* 98 */
3409 		"-", /* 99 */
3410 		"-", /* 9A */
3411 		"-", /* 9B */
3412 		"-", /* 9C */
3413 		"-", /* 9D */
3414 		"-", /* 9E */
3415 		"-", /* 9F */
3416 		"-", /* A0 */
3417 		"-", /* A1 */
3418 		"-", /* A2 */
3419 		"-", /* A3 */
3420 		"-", /* A4 */
3421 		"-", /* A5 */
3422 		"-", /* A6 */
3423 		"-", /* A7 */
3424 		"-", /* A8 */
3425 		"-", /* A9 */
3426 		"-", /* AA */
3427 		"-", /* AB */
3428 		"-", /* AC */
3429 		"-", /* AD */
3430 		"-", /* AE */
3431 		"-", /* AF */
3432 		"-", /* B0 */
3433 		"-", /* B1 */
3434 		"-", /* B2 */
3435 		"-", /* B3 */
3436 		"-", /* B4 */
3437 		"-", /* B5 */
3438 		"-", /* B6 */
3439 		"-", /* B7 */
3440 		"-", /* B8 */
3441 		"-", /* B9 */
3442 		"-", /* BA */
3443 		"-", /* BB */
3444 		"-", /* BC */
3445 		"-", /* BD */
3446 		"-", /* BE */
3447 		"-", /* BF */
3448 		"-", /* C0 */
3449 		"-", /* C1 */
3450 		"-", /* C2 */
3451 		"-", /* C3 */
3452 		"-", /* C4 */
3453 		"-", /* C5 */
3454 		"-", /* C6 */
3455 		"-", /* C7 */
3456 		"-", /* C8 */
3457 		"-", /* C9 */
3458 		"-", /* CA */
3459 		"-", /* CB */
3460 		"-", /* CC */
3461 		"-", /* CD */
3462 		"-", /* CE */
3463 		"-", /* CF */
3464 		"-", /* D0 */
3465 		"-", /* D1 */
3466 		"-", /* D2 */
3467 		"-", /* D3 */
3468 		"-", /* D4 */
3469 		"-", /* D5 */
3470 		"-", /* D6 */
3471 		"-", /* D7 */
3472 		"-", /* D8 */
3473 		"-", /* D9 */
3474 		"-", /* DA */
3475 		"-", /* DB */
3476 		"-", /* DC */
3477 		"-", /* DD */
3478 		"-", /* DE */
3479 		"-", /* DF */
3480 		"-", /* E0 */
3481 		"-", /* E1 */
3482 		"-", /* E2 */
3483 		"-", /* E3 */
3484 		"-", /* E4 */
3485 		"-", /* E5 */
3486 		"-", /* E6 */
3487 		"-", /* E7 */
3488 		"-", /* E8 */
3489 		"-", /* E9 */
3490 		"-", /* EA */
3491 		"-", /* EB */
3492 		"-", /* EC */
3493 		"-", /* ED */
3494 		"-", /* EE */
3495 		"-", /* EF */
3496 		"-", /* F0 */
3497 		"-", /* F1 */
3498 		"-", /* F2 */
3499 		"-", /* F3 */
3500 		"-", /* F4 */
3501 		"-", /* F5 */
3502 		"-", /* F6 */
3503 		"-", /* F7 */
3504 		"-", /* F8 */
3505 		"-", /* F9 */
3506 		"-", /* FA */
3507 		"-", /* FB */
3508 		"-", /* FC */
3509 		"-", /* FD */
3510 		"-", /* FE */
3511 		"Syield", /* FF */
3512 		"Fpipe", /* 100 */
3513 		"Ffchown", /* 101 */
3514 		"Ffchmod", /* 102 */
3515 		"Fsync", /* 103 */
3516 		"Fcntl", /* 104 */
3517 		"Finstat", /* 105 */
3518 		"Foutstat", /* 106 */
3519 		"Fgetchar", /* 107 */
3520 		"Fputchar", /* 108 */
3521 		"Pwait", /* 109 */
3522 		"Pnice", /* 10A */
3523 		"Pgetpid", /* 10B */
3524 		"Pgetppid", /* 10C */
3525 		"Pgetpgrp", /* 10D */
3526 		"Psetpgrp", /* 10E */
3527 		"Pgetuid", /* 10F */
3528 		"Psetuid", /* 110 */
3529 		"Pkill", /* 111 */
3530 		"Psignal", /* 112 */
3531 		"Pvfork", /* 113 */
3532 		"Pgetgid", /* 114 */
3533 		"Psetgid", /* 115 */
3534 		"Psigblock", /* 116 */
3535 		"Psigsetmask", /* 117 */
3536 		"Pusrval", /* 118 */
3537 		"Pdomain", /* 119 */
3538 		"Psigreturn", /* 11A */
3539 		"Pfork", /* 11B */
3540 		"Pwait3", /* 11C */
3541 		"Fselect", /* 11D */
3542 		"Prusage", /* 11E */
3543 		"Psetlimit", /* 11F */
3544 		"Talarm", /* 120 */
3545 		"Pause", /* 121 */
3546 		"Sysconf", /* 122 */
3547 		"Psigpending", /* 123 */
3548 		"Dpathconf", /* 124 */
3549 		"Pmsg", /* 125 */
3550 		"Fmidipipe", /* 126 */
3551 		"Prenice", /* 127 */
3552 		"Dopendir", /* 128 */
3553 		"Dreaddir", /* 129 */
3554 		"Drewinddir", /* 12A */
3555 		"Dclosedir", /* 12B */
3556 		"Fxattr", /* 12C */
3557 		"Flink", /* 12D */
3558 		"Fsymlink", /* 12E */
3559 		"Freadlink", /* 12F */
3560 		"Dcntl", /* 130 */
3561 		"Fchown", /* 131 */
3562 		"Fchmod", /* 132 */
3563 		"Pumask", /* 133 */
3564 		"Psemaphore", /* 134 */
3565 		"Dlock", /* 135 */
3566 		"Psigpause", /* 136 */
3567 		"Psigaction", /* 137 */
3568 		"Pgeteuid", /* 138 */
3569 		"Pgetegid", /* 139 */
3570 		"Pwaitpid", /* 13A */
3571 		"Dgetcwd", /* 13B */
3572 		"Salert", /* 13C */
3573 		"Tmalarm", /* 13D */
3574 		"Psigintr", /* 13E */
3575 		"Suptime", /* 13F */
3576 		"Ptrace", /* 140 */
3577 		"Mvalidate", /* 141 */
3578 		"Dxreaddir", /* 142 */
3579 		"Pseteuid", /* 143 */
3580 		"Psetegid", /* 144 */
3581 		"Pgetauid", /* 145 */
3582 		"Psetauid", /* 146 */
3583 		"Pgetgroups", /* 147 */
3584 		"Psetgroups", /* 148 */
3585 		"Tsetitimer", /* 149 */
3586 		"Dchroot", /* 14A; was Scookie */
3587 		"Fstat64", /* 14B */
3588 		"Fseek64", /* 14C */
3589 		"Dsetkey", /* 14D */
3590 		"Psetreuid", /* 14E */
3591 		"Psetregid", /* 14F */
3592 		"Sync", /* 150 */
3593 		"Shutdown", /* 151 */
3594 		"Dreadlabel", /* 152 */
3595 		"Dwritelabel", /* 153 */
3596 		"Ssystem", /* 154 */
3597 		"Tgettimeofday", /* 155 */
3598 		"Tsettimeofday", /* 156 */
3599 		"Tadjtime", /* 157 */
3600 		"Pgetpriority", /* 158 */
3601 		"Psetpriority", /* 159 */
3602 		"Fpoll", /* 15A */
3603 		"Fwritev", /* 15B */
3604 		"Freadv", /* 15C */
3605 		"Ffstat64", /* 15D */
3606 		"Psysctl", /* 15E */
3607 		"Semulation", /* 15F */
3608 		"Fsocket", /* 160 */
3609 		"Fsocketpair", /* 161 */
3610 		"Faccept", /* 162 */
3611 		"Fconnect", /* 163 */
3612 		"Fbind", /* 164 */
3613 		"Flisten", /* 165 */
3614 		"Frecvmsg", /* 166 */
3615 		"Fsendmsg", /* 167 */
3616 		"Frecvfrom", /* 168 */
3617 		"Fsendto", /* 169 */
3618 		"Fsetsockopt", /* 16A */
3619 		"Fgetsockopt", /* 16B */
3620 		"Fgetpeername", /* 16C */
3621 		"Fgetsockname", /* 16D */
3622 		"Fshutdown", /* 16E */
3623 		"-", /* 16F */
3624 		"Pshmget", /* 170 */
3625 		"Pshmctl", /* 171 */
3626 		"Pshmat", /* 172 */
3627 		"Pshmdt", /* 173 */
3628 		"Psemget", /* 174 */
3629 		"Psemctl", /* 175 */
3630 		"Psemop", /* 176 */
3631 		"Psemconfig", /* 177 */
3632 		"Pmsgget", /* 178 */
3633 		"Pmsgctl", /* 179 */
3634 		"Pmsgsnd", /* 17A */
3635 		"Pmsgrcv", /* 17B */
3636 		"-", /* 17C */
3637 		"Maccess", /* 17D */
3638 		"-", /* 17E */
3639 		"-", /* 17F */
3640 		"Fchown16", /* 180 */
3641 		"Fchdir", /* 181 */
3642 		"Ffdopendir", /* 182 */
3643 		"Fdirfd" /* 183 */
3644 	};
3645 
3646 	if (opcode < ARRAY_SIZE(names))
3647 		return names[opcode];
3648 	return "-";
3649 }
3650 
3651 
3652 /**
3653  * If bShowOpcodes is true, show GEMDOS call opcode/function name table,
3654  * otherwise GEMDOS HDD emulation information.
3655  */
GemDOS_Info(FILE * fp,Uint32 bShowOpcodes)3656 void GemDOS_Info(FILE *fp, Uint32 bShowOpcodes)
3657 {
3658 	int i, used;
3659 
3660 	if (bShowOpcodes)
3661 	{
3662 		Uint16 opcode;
3663 		/* list just normal TOS GEMDOS calls
3664 		 *
3665 		 * MiNT ones would need separate table as their names
3666 		 * are much longer and 0x60 - 0xFE range is unused.
3667 		 */
3668 		for (opcode = 0; opcode < 0x5A; )
3669 		{
3670 			fprintf(fp, "%02x %-9s",
3671 				opcode, GemDOS_Opcode2Name(opcode));
3672 			if (++opcode % 6 == 0)
3673 				fputs("\n", fp);
3674 		}
3675 		return;
3676 	}
3677 
3678 	if (!GEMDOS_EMU_ON)
3679 	{
3680 		fputs("GEMDOS HDD emulation isn't enabled!\n", fp);
3681 		return;
3682 	}
3683 
3684 	/* GEMDOS vector set by Hatari can be overwritten e.g. MiNT */
3685 	fprintf(fp, "Current GEMDOS handler: (0x84) = 0x%x, emu one = 0x%x\n", STMemory_ReadLong(0x0084), CART_GEMDOS);
3686 	fprintf(fp, "Stored GEMDOS handler: (0x%x) = 0x%x\n\n", CART_OLDGEMDOS, STMemory_ReadLong(CART_OLDGEMDOS));
3687 
3688 	fprintf(fp, "Connected drives mask: 0x%x\n\n", ConnectedDriveMask);
3689 	fputs("GEMDOS HDD emulation drives:\n", fp);
3690 	for(i = 0; i < MAX_HARDDRIVES; i++)
3691 	{
3692 		if (!emudrives[i])
3693 			continue;
3694 		fprintf(fp, "- %c: %s\n  curpath: %s\n",
3695 			'A' + emudrives[i]->drive_number,
3696 			emudrives[i]->hd_emulation_dir,
3697 			emudrives[i]->fs_currpath);
3698 	}
3699 
3700 	fputs("\nInternal Fsfirst() DTAs:\n", fp);
3701 	for(used = i = 0; i < ARRAY_SIZE(InternalDTAs); i++)
3702 	{
3703 		int j, centry, entries;
3704 
3705 		if (!InternalDTAs[i].bUsed)
3706 			continue;
3707 
3708 		fprintf(fp, "+ %d: %s\n", i, InternalDTAs[i].path);
3709 
3710 		centry = InternalDTAs[i].centry;
3711 		entries = InternalDTAs[i].nentries;
3712 		for (j = 0; j < entries; j++)
3713 		{
3714 			fprintf(fp, "  - %d: %s%s\n",
3715 				j, InternalDTAs[i].found[j]->d_name,
3716 				j == centry ? " *" : "");
3717 		}
3718 		fprintf(fp, "  Fsnext entry = %d.\n", centry);
3719 		used++;
3720 	}
3721 	if (!used)
3722 		fputs("- None in use.\n", fp);
3723 
3724 	fputs("\nOpen GEMDOS HDD file handles:\n", fp);
3725 	for (used = i = 0; i < ARRAY_SIZE(FileHandles); i++)
3726 	{
3727 		if (!FileHandles[i].bUsed)
3728 			continue;
3729 		fprintf(fp, "- %d (0x%x): %s\n", i + BASE_FILEHANDLE,
3730 			FileHandles[i].Basepage, FileHandles[i].szActualName);
3731 		used++;
3732 	}
3733 	if (!used)
3734 		fputs("- None.\n", fp);
3735 	fputs("\nForced GEMDOS HDD file handles:\n", fp);
3736 	for (used = i = 0; i < ARRAY_SIZE(ForcedHandles); i++)
3737 	{
3738 		if (ForcedHandles[i].Handle == UNFORCED_HANDLE)
3739 			continue;
3740 		fprintf(fp, "- %d -> %d (0x%x)\n", i,
3741 			ForcedHandles[i].Handle + BASE_FILEHANDLE,
3742 			ForcedHandles[i].Basepage);
3743 		used++;
3744 	}
3745 	if (!used)
3746 		fputs("- None.\n", fp);
3747 }
3748 
3749 /**
3750  * Show given DTA info
3751  * (works also without GEMDOS HD emu)
3752  */
GemDOS_InfoDTA(FILE * fp,Uint32 dta_addr)3753 void GemDOS_InfoDTA(FILE *fp, Uint32 dta_addr)
3754 {
3755 	DTA *dta;
3756 	Uint32 magic;
3757 	char name[TOS_NAMELEN+1];
3758 
3759 	fprintf(fp, "DTA (0x%x):\n", dta_addr);
3760 	if (act_pd)
3761 	{
3762 		Uint32 basepage = STMemory_ReadLong(act_pd);
3763 		Uint32 dta_curr = STMemory_ReadLong(basepage + BASEPAGE_OFFSET_DTA);
3764 		if (dta_addr != dta_curr)
3765 		{
3766 			fprintf(fp, "- NOTE: given DTA (0x%x) is not current program one (0x%x)\n",
3767 				dta_addr, dta_curr);
3768 		}
3769 		if (dta_addr >= basepage && dta_addr + sizeof(DTA) < basepage + BASEPAGE_SIZE)
3770 		{
3771 			const char *msg = (dta_addr == basepage + 0x80) ? ", replacing command line" : "";
3772 			fprintf(fp, "- NOTE: DTA (0x%x) is within current program basepage (0x%x)%s!\n",
3773 				dta_addr, basepage, msg);
3774 		}
3775 	}
3776 	if (!STMemory_CheckAreaType(dta_addr, sizeof(DTA), ABFLAG_RAM)) {
3777 		fprintf(fp, "- ERROR: invalid memory address!\n");
3778 		return;
3779 	}
3780 	dta = (DTA *)STMemory_STAddrToPointer(dta_addr);
3781 	memcpy(name, dta->dta_name, TOS_NAMELEN);
3782 	name[TOS_NAMELEN] = '\0';
3783 	magic = do_get_mem_long(dta->magic);
3784 	fprintf(fp, "- magic: 0x%08x (GEMDOS HD = 0x%08x)\n", magic, DTA_MAGIC_NUMBER);
3785 	if (magic == DTA_MAGIC_NUMBER)
3786 		fprintf(fp, "- index: 0x%04x\n", do_get_mem_word(dta->index));
3787 	fprintf(fp, "- attr: 0x%x\n", dta->dta_attrib);
3788 	fprintf(fp, "- time: 0x%04x\n", do_get_mem_word(dta->dta_time));
3789 	fprintf(fp, "- date: 0x%04x\n", do_get_mem_word(dta->dta_date));
3790 	fprintf(fp, "- size: %d\n", do_get_mem_long(dta->dta_size));
3791 	fprintf(fp, "- name: '%s'\n", name);
3792 }
3793 
3794 
3795 /**
3796  * Run GEMDos call, and re-direct if need to. Used to handle hard disk emulation etc...
3797  * This sets the condition codes (in SR), which are used in the 'cart_asm.s' program to
3798  * decide if we need to run old GEM vector, or PExec or nothing.
3799  *
3800  * This method keeps the stack and other states consistent with the original ST
3801  * which is very important for the PExec call and maximum compatibility through-out
3802  */
GemDOS_OpCode(void)3803 void GemDOS_OpCode(void)
3804 {
3805 	Uint16 GemDOSCall, CallingSReg;
3806 	Uint32 Params;
3807 	int Finished;
3808 	Uint16 SR;
3809 
3810 	SR = M68000_GetSR();
3811 
3812 	/* Read SReg from stack to see if parameters are on User or Super stack  */
3813 	CallingSReg = STMemory_ReadWord(Regs[REG_A7]);
3814 	CallingPC = STMemory_ReadLong(Regs[REG_A7]+SIZE_WORD);
3815 	if ((CallingSReg&SR_SUPERMODE)==0)      /* Calling from user mode */
3816 		Params = regs.usp;
3817 	else
3818 	{
3819 		Params = Regs[REG_A7]+SIZE_WORD+SIZE_LONG;  /* skip SR & PC pushed to super stack */
3820 		if (currprefs.cpu_level > 0)
3821 			Params += SIZE_WORD;   /* Skip extra word whe CPU is >=68010 */
3822 	}
3823 
3824 	/* Default to run TOS GemDos (SR_NEG run Gemdos, SR_ZERO already done, SR_OVERFLOW run own 'Pexec' */
3825 	Finished = false;
3826 	SR &= SR_CLEAR_OVERFLOW;
3827 	SR &= SR_CLEAR_ZERO;
3828 	SR |= SR_NEG;
3829 
3830 	/* Find pointer to call parameters */
3831 	GemDOSCall = STMemory_ReadWord(Params);
3832 	Params += SIZE_WORD;
3833 
3834 	/* Intercept call */
3835 	switch(GemDOSCall)
3836 	{
3837 	 case 0x00:
3838 		Finished = GemDOS_Pterm0(Params);
3839 		break;
3840 	 case 0x09:
3841 		Finished = GemDOS_Cconws(Params);
3842 		break;
3843 	 case 0x0e:
3844 		Finished = GemDOS_SetDrv(Params);
3845 		break;
3846 	 case 0x20:
3847 		Finished = GemDOS_Super(Params);
3848 		break;
3849 	 case 0x31:
3850 		Finished = GemDOS_Ptermres(Params);
3851 		break;
3852 	 case 0x36:
3853 		Finished = GemDOS_DFree(Params);
3854 		break;
3855 	 case 0x39:
3856 		Finished = GemDOS_MkDir(Params);
3857 		break;
3858 	 case 0x3a:
3859 		Finished = GemDOS_RmDir(Params);
3860 		break;
3861 	 case 0x3b:	/* Dsetpath */
3862 		Finished = GemDOS_ChDir(Params);
3863 		break;
3864 	 case 0x3c:
3865 		Finished = GemDOS_Create(Params);
3866 		break;
3867 	 case 0x3d:
3868 		Finished = GemDOS_Open(Params);
3869 		break;
3870 	 case 0x3e:
3871 		Finished = GemDOS_Close(Params);
3872 		break;
3873 	 case 0x3f:
3874 		Finished = GemDOS_Read(Params);
3875 		break;
3876 	 case 0x40:
3877 		Finished = GemDOS_Write(Params);
3878 		break;
3879 	 case 0x41:
3880 		Finished = GemDOS_FDelete(Params);
3881 		break;
3882 	 case 0x42:
3883 		Finished = GemDOS_LSeek(Params);
3884 		break;
3885 	 case 0x43:
3886 		Finished = GemDOS_Fattrib(Params);
3887 		break;
3888 	 case 0x46:
3889 		Finished = GemDOS_Force(Params);
3890 		break;
3891 	 case 0x47:	/* Dgetpath */
3892 		Finished = GemDOS_GetDir(Params);
3893 		break;
3894 	 case 0x4b:
3895 		/* Either false or CALL_PEXEC_ROUTINE */
3896 		Finished = GemDOS_Pexec(Params);
3897 		break;
3898 	 case 0x4c:
3899 		Finished = GemDOS_Pterm(Params);
3900 		break;
3901 	 case 0x4e:
3902 		Finished = GemDOS_SFirst(Params);
3903 		break;
3904 	 case 0x4f:
3905 		Finished = GemDOS_SNext();
3906 		break;
3907 	 case 0x56:
3908 		Finished = GemDOS_Rename(Params);
3909 		break;
3910 	 case 0x57:
3911 		Finished = GemDOS_GSDToF(Params);
3912 		break;
3913 
3914 	/* print args for other calls */
3915 
3916 	case 0x01:	/* Conin */
3917 	case 0x03:	/* Cauxin */
3918 	case 0x12:	/* Cauxis */
3919 	case 0x13:	/* Cauxos */
3920 	case 0x0B:	/* Conis */
3921 	case 0x10:	/* Conos */
3922 	case 0x08:	/* Cnecin */
3923 	case 0x11:	/* Cprnos */
3924 	case 0x07:	/* Crawcin */
3925 	case 0x19:	/* Dgetdrv */
3926 	case 0x2F:	/* Fgetdta */
3927 	case 0x30:	/* Sversion */
3928 	case 0x2A:	/* Tgetdate */
3929 	case 0x2C:	/* Tgettime */
3930 		/* commands with no args */
3931 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s() at PC 0x%X\n",
3932 			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3933 			  CallingPC);
3934 		break;
3935 
3936 	case 0x02:	/* Cconout */
3937 	case 0x04:	/* Cauxout */
3938 	case 0x05:	/* Cprnout */
3939 	case 0x06:	/* Crawio */
3940 	case 0x2b:	/* Tsetdate */
3941 	case 0x2d:	/* Tsettime */
3942 	case 0x45:	/* Fdup */
3943 		/* commands taking single word */
3944 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s(0x%hX) at PC 0x%X\n",
3945 			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3946 			  STMemory_ReadWord(Params),
3947 			  CallingPC);
3948 		break;
3949 
3950 	case 0x0A:	/* Cconrs */
3951 	case 0x1A:	/* Fsetdta */
3952 	case 0x48:	/* Malloc */
3953 	case 0x49:	/* Mfree */
3954 		/* commands taking long/pointer */
3955 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX %s(0x%X) at PC 0x%X\n",
3956 			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3957 			  STMemory_ReadLong(Params),
3958 			  CallingPC);
3959 		break;
3960 
3961 	case 0x44:	/* Mxalloc */
3962 		/* commands taking long/pointer + word */
3963 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x44 Mxalloc(0x%X, 0x%hX) at PC 0x%X\n",
3964 			  STMemory_ReadLong(Params),
3965 			  STMemory_ReadWord(Params+SIZE_LONG),
3966 			  CallingPC);
3967 		break;
3968 	case 0x14:	/* Maddalt */
3969 		/* commands taking 2 longs/pointers */
3970 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x14 Maddalt(0x%X, 0x%X) at PC 0x%X\n",
3971 			  STMemory_ReadLong(Params),
3972 			  STMemory_ReadLong(Params+SIZE_LONG),
3973 			  CallingPC);
3974 		break;
3975 	case 0x4A:	/* Mshrink */
3976 		/* Mshrink's two pointers are prefixed by reserved zero word:
3977 		 * http://toshyp.atari.org/en/00500c.html#Bindings_20for_20Mshrink
3978 		 */
3979 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x4A Mshrink(0x%X, 0x%X) at PC 0x%X\n",
3980 			  STMemory_ReadLong(Params+SIZE_WORD),
3981 			  STMemory_ReadLong(Params+SIZE_WORD+SIZE_LONG),
3982 			  CallingPC);
3983 		if (!bUseTos)
3984 			Finished = true;
3985 		break;
3986 
3987 	default:
3988 		/* rest of commands */
3989 		LOG_TRACE(TRACE_OS_GEMDOS, "GEMDOS 0x%02hX (%s) at PC 0x%X\n",
3990 			  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
3991 			  CallingPC);
3992 	}
3993 
3994 	switch(Finished)
3995 	{
3996 	 case true:
3997 		/* skip over branch to pexec to RTE */
3998 		SR |= SR_ZERO;
3999 		/* visualize GemDOS emu HD access? */
4000 		switch (GemDOSCall)
4001 		{
4002 		 case 0x36:
4003 		 case 0x39:
4004 		 case 0x3a:
4005 		 case 0x3b:
4006 		 case 0x3c:
4007 		 case 0x3d:
4008 		 case 0x3e:
4009 		 case 0x3f:
4010 		 case 0x40:
4011 		 case 0x41:
4012 		 case 0x42:
4013 		 case 0x43:
4014 		 case 0x47:
4015 		 case 0x4e:
4016 		 case 0x4f:
4017 		 case 0x56:
4018 			Statusbar_EnableHDLed( LED_STATE_ON );
4019 		}
4020 		break;
4021 	 case CALL_PEXEC_ROUTINE:
4022 		/* branch to pexec, then redirect to old gemdos. */
4023 		SR |= SR_OVERFLOW;
4024 		break;
4025 	 case false:
4026 		if (!bUseTos)
4027 		{
4028 			if (GemDOSCall >= 0x58)   /* Ignore optional calls */
4029 			{
4030 				SR |= SR_ZERO;
4031 				Regs[REG_D0] = GEMDOS_EINVFN;
4032 				break;
4033 			}
4034 			Log_Printf(LOG_FATAL, "GEMDOS 0x%02hX %s at PC 0x%X unsupported in test mode\n",
4035 				  GemDOSCall, GemDOS_Opcode2Name(GemDOSCall),
4036 				  CallingPC);
4037 			Main_SetQuitValue(1);
4038 		}
4039 		break;
4040 	}
4041 
4042 	M68000_SetSR(SR);   /* update the flags in the SR register */
4043 }
4044 
4045 
4046 /*-----------------------------------------------------------------------*/
4047 /**
4048  * GemDOS_Boot - routine called on the first occurrence of the gemdos opcode.
4049  * (this should be in the cartridge bootrom)
4050  * Sets up our gemdos handler (or, if we don't need one, just turn off keyclicks)
4051  */
GemDOS_Boot(void)4052 void GemDOS_Boot(void)
4053 {
4054 	if (bInitGemDOS)
4055 		GemDOS_Reset();
4056 
4057 	bInitGemDOS = true;
4058 
4059 	LOG_TRACE(TRACE_OS_GEMDOS, "Gemdos_Boot() at PC 0x%X\n", M68000_GetPC() );
4060 
4061 	/* install our gemdos handler, if user has enabled either
4062 	 * GEMDOS HD, autostarting or GEMDOS tracing
4063 	 */
4064 	if (!GEMDOS_EMU_ON &&
4065 	    !INF_Overriding(AUTOSTART_INTERCEPT) &&
4066 	    !(LogTraceFlags & (TRACE_OS_GEMDOS|TRACE_OS_BASE)))
4067 		return;
4068 
4069 	/* Get the address of the p_run variable that points to the actual basepage */
4070 	if (TosVersion == 0x100)
4071 	{
4072 		/* We have to use fix addresses on TOS 1.00 :-( */
4073 		if ((STMemory_ReadWord(TosAddress+28)>>1) == 4)
4074 			act_pd = 0x873c;    /* Spanish TOS is different from others! */
4075 		else
4076 			act_pd = 0x602c;
4077 	}
4078 	else
4079 	{
4080 		Uint32 osAddress = STMemory_ReadLong(0x4f2);
4081 		act_pd = STMemory_ReadLong(osAddress + 0x28);
4082 	}
4083 
4084 	/* Save old GEMDOS handler address */
4085 	STMemory_WriteLong(CART_OLDGEMDOS, STMemory_ReadLong(0x0084));
4086 	/* Setup new GEMDOS handler, see "cart_asm.s" */
4087 	STMemory_WriteLong(0x0084, CART_GEMDOS);
4088 }
4089 
4090 
4091 /**
4092  * Load and relocate a PRG file into the memory of the emulated machine.
4093  */
GemDOS_LoadAndReloc(const char * psPrgName,uint32_t baseaddr)4094 int GemDOS_LoadAndReloc(const char *psPrgName, uint32_t baseaddr)
4095 {
4096 	long nFileSize, nRelTabIdx;
4097 	uint8_t *prg;
4098 	uint32_t nTextLen, nDataLen, nBssLen, nSymLen;
4099 	uint32_t nRelOff, nCurrAddr;
4100 	uint32_t memtop;
4101 
4102 	prg = File_Read(psPrgName, &nFileSize, NULL);
4103 	if (!prg || nFileSize < 30)
4104 	{
4105 		Log_Printf(LOG_ERROR, "Failed to load '%s'.\n", psPrgName);
4106 		return -1;
4107 	}
4108 
4109 	if (prg[0] != 0x60 || prg[1] != 0x1a)  /* Check PRG magic */
4110 	{
4111 		Log_Printf(LOG_ERROR, "The file '%s' is not a valid PRG.\n", psPrgName);
4112 		return -1;
4113 	}
4114 
4115 	nTextLen = (prg[2] << 24) | (prg[3] << 16) | (prg[4] << 8) | prg[5];
4116 	nDataLen = (prg[6] << 24) | (prg[7] << 16) | (prg[8] << 8) | prg[9];
4117 	nBssLen = (prg[10] << 24) | (prg[11] << 16) | (prg[12] << 8) | prg[13];
4118 	nSymLen = (prg[14] << 24) | (prg[15] << 16) | (prg[16] << 8) | prg[17];
4119 
4120 	memtop = STMemory_ReadLong(0x436);
4121 	if (baseaddr + 0x100 + nTextLen + nDataLen + nBssLen > memtop)
4122 	{
4123 		Log_Printf(LOG_ERROR, "Program too large: '%s'.\n", psPrgName);
4124 		return -1;
4125 	}
4126 
4127 	if (!STMemory_SafeCopy(baseaddr + 0x100, prg + 28, nTextLen + nDataLen, psPrgName))
4128 		return -1;
4129 
4130 	/* Clear BSS */
4131 	if (!STMemory_SafeClear(baseaddr + 0x100 + nTextLen + nDataLen, nBssLen))
4132 	{
4133 		Log_Printf(LOG_ERROR, "Failed to clear BSS for '%s'.\n", psPrgName);
4134 		return -1;
4135 	}
4136 
4137 	/* Set up basepage - note: some of these values are rather dummies */
4138 	STMemory_WriteLong(baseaddr, baseaddr);                                    /* p_lowtpa */
4139 	STMemory_WriteLong(baseaddr + 4, memtop);                                  /* p_hitpa */
4140 	STMemory_WriteLong(baseaddr + 8, baseaddr + 0x100);                        /* p_tbase */
4141 	STMemory_WriteLong(baseaddr + 12, nTextLen);                               /* p_tlen */
4142 	STMemory_WriteLong(baseaddr + 16, baseaddr + 0x100 + nTextLen);            /* p_dbase */
4143 	STMemory_WriteLong(baseaddr + 20, nDataLen);                               /* p_dlen */
4144 	STMemory_WriteLong(baseaddr + 24, baseaddr + 0x100 + nTextLen + nDataLen); /* p_bbase */
4145 	STMemory_WriteLong(baseaddr + 28, nBssLen);                                /* p_blen */
4146 	STMemory_WriteLong(baseaddr + 32, baseaddr + 0x80);                        /* p_dta */
4147 	STMemory_WriteLong(baseaddr + 36, baseaddr);                               /* p_parent */
4148 	STMemory_WriteLong(baseaddr + 40, 0);                                      /* p_reserved */
4149 	/* The environment should point to an empty string - use p_reserved for that: */
4150 	STMemory_WriteLong(baseaddr + 44, baseaddr + 40);                          /* p_env */
4151 
4152 	if (*(uint16_t *)&prg[26] != 0)   /* No reloc information available? */
4153 		return 0;
4154 
4155 	nRelTabIdx = 0x1c + nTextLen + nDataLen + nSymLen;
4156 	if (nRelTabIdx > nFileSize - 3)
4157 	{
4158 		Log_Printf(LOG_ERROR, "Can not parse relocation table of '%s'.\n", psPrgName);
4159 		return -1;
4160 	}
4161 	nRelOff = (prg[nRelTabIdx] << 24) | (prg[nRelTabIdx + 1] << 16)
4162 	          | (prg[nRelTabIdx + 2] << 8) | prg[nRelTabIdx + 3];
4163 
4164 	if (nRelOff == 0)
4165 		return 0;
4166 
4167 	nCurrAddr = baseaddr + 0x100 + nRelOff;
4168 	STMemory_WriteLong(nCurrAddr, STMemory_ReadLong(nCurrAddr) + baseaddr + 0x100);
4169 	nRelTabIdx += 4;
4170 
4171 	while (nRelTabIdx < nFileSize && prg[nRelTabIdx])
4172 	{
4173 		if (prg[nRelTabIdx] == 1)
4174 		{
4175 			nRelOff += 254;
4176 			nRelTabIdx += 1;
4177 			continue;
4178 		}
4179 		nRelOff += prg[nRelTabIdx];
4180 		nCurrAddr = baseaddr + 0x100 + nRelOff;
4181 		STMemory_WriteLong(nCurrAddr, STMemory_ReadLong(nCurrAddr) + baseaddr + 0x100);
4182 		nRelTabIdx += 1;
4183 	}
4184 
4185 	if (nRelTabIdx >= nFileSize)
4186 	{
4187 		Log_Printf(LOG_ERROR, "Failed to parse relocation table of '%s'.\n", psPrgName);
4188 		return -1;
4189 	}
4190 
4191 	return 0;
4192 }
4193