Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
cpmfs.c
Go to the documentation of this file.
1 /* Re-CP/M: CP/M-like own implementation + Z80 emulator
2  Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3  Copyright (C)2016-2019 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
18 
19 #include "xemu/emutools.h"
20 #include "cpmfs.h"
21 #include "hardware.h"
22 #include "bdos.h"
23 #include <dirent.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 
29 #include "console.h"
30 
31 
32 #define MAX_OPEN_FILES 32
33 
34 static struct {
35  char pattern[8 + 3 + 1]; // FCB formatted pattern [ie, the file name we search for, probbaly with '?' wildcard chars]
36  char found[8 + 3 + 1]; // FCB formatted filename of the current found item
37  char host_name[13]; // host OS filename found [not CP/M!], must be 8 + 3 + 1 + 1 =
38  char host_path[PATH_MAX]; // host OS full-path filename found
41  int options;
42  int drive;
43  struct stat st;
44 } ff;
45 static struct {
46  DIR *dir; // directory stream, NULL if not "mounted"
47  char dir_path[PATH_MAX]; // host OS directory of the drive (with ALWAYS a trailing dirsep char!), or null string (ie dir_path[0] = 0) if not "mounted"
48  int ro; // drive is software write-protected, ie "read-only"
49 } drives[26];
50 static struct {
51  int fd;
52  Uint8 checksum[0x10];
53  int drive;
55  int sequence;
56 } files[MAX_OPEN_FILES];
57 
59 
60 
61 #ifdef XEMU_ARCH_WIN
62 #define realpath(r,a) _fullpath(a,r,PATH_MAX)
63 #endif
64 
65 
66 
67 void cpmfs_init ( void )
68 {
69  ff.result_is_valid = 0;
70  ff.stop_search = 1;
71  current_drive = -1;
72  for (int a = 0; a < 26; a++) {
73  drives[a].dir_path[0] = 0;
74  drives[a].dir = NULL;
75  }
76  for (int a = 0; a < MAX_OPEN_FILES; a++)
77  files[a].fd = -1;
78  DEBUGPRINT("CPMFS: initialized, %d max open files, PATH_MAX=%d" NL, MAX_OPEN_FILES, PATH_MAX);
79 }
80 
81 
82 void cpmfs_close_all_files ( void )
83 {
84  for (int a = 0; a < MAX_OPEN_FILES; a++)
85  if (files[a].fd >= 0) {
86  close(files[a].fd);
87  files[a].fd = -1;
88  }
89 }
90 
91 void cpmfs_uninit ( void )
92 {
94  for (int a = 0; a < 26; a++)
95  if (drives[a].dir)
96  closedir(drives[a].dir);
97 }
98 
99 
100 
101 int cpmfs_mount_drive ( int drive, const char *dir_path, int dirbase_part_only )
102 {
103  if (drive >= 26 || drive < 0)
104  return 1;
105  if (!dir_path || !dir_path[0]) {
106  drives[drive].dir_path[0] = 0;
107  if (drives[drive].dir) {
108  closedir(drives[drive].dir);
109  drives[drive].dir = NULL;
110  conprintf("CPMFS: drive %c has been umounted\r\n", drive + 'A');
111  }
112  return 0;
113  }
114  char path_resolved[PATH_MAX];
115  if (!realpath(dir_path, path_resolved)) {
116  conprintf("CPMFS: drive %c cannot be mounted, realpath() failure\r\n", drive + 'A');
117  return 1;
118  }
119  if (dirbase_part_only) {
120  char *p = strrchr(path_resolved, DIRSEP_CHR);
121  if (!p)
122  return 1;
123  *p = 0;
124  }
125  int len = strlen(path_resolved);
126  if (len > PATH_MAX - 14) {
127  conprintf("CPMFS: drive %c cannot be mounted, too long path\r\n", drive + 'A');
128  return 1;
129  }
130  DIR *dir = opendir(path_resolved);
131  if (!dir) {
132  conprintf("CPMFS: drive %c cannot be mounted, host directory cannot be open\r\n", drive + 'A');
133  return 1;
134  }
135  if (path_resolved[len - 1] != DIRSEP_CHR) {
136  path_resolved[len++] = DIRSEP_CHR;
137  path_resolved[len] = 0;
138  }
139  memcpy(drives[drive].dir_path, path_resolved, len + 1); // copy the terminator \0 too (+1)
140  if (drives[drive].dir) {
141  closedir(drives[drive].dir);
142  conprintf("CPMFS: drive %c remounting.\r\n", drive + 'A');
143  }
144  conprintf("CPMFS: drive %c <- %s\r\n", drive + 'A', path_resolved);
145  drives[drive].dir = dir;
146  drives[drive].ro = 0;
147  if (current_drive == -1) {
149  conprintf("CPMFS: drive %c has been selected as the current (first mount)\r\n", drive + 'A');
150  }
151  return 0;
152 }
153 
155 {
156  return ff.result_is_valid ? ff.host_path : NULL;
157 }
158 
159 
160 static int fn_part ( char *dest, const char *src, int len, int maxlen, int jokery )
161 {
162  if (len > maxlen)
163  return 1;
164  for (int a = 0, c = 32; a < maxlen; a++) {
165  if (a < len) {
166  c = src[a];
167  if (c == '*') { // technically this shouldn't allowed to be on FCB for search, but we also use for internal parsing not on CP/M level but in our C code
168  if (jokery) {
169  c = '?';
170  len = 0; // to trick parser to stop for the rest and give only '?'
171  *dest++ = c;
172  continue;
173  } else
174  return 1;
175  } else if (c == '?') {
176  if (!jokery)
177  return 1;
178  *dest++ = c;
179  } else if (c >= 'a' && c <= 'z') {
180  *dest++ = c - 'a' + 'A';
181  } else if (c < 32 || c >= 127) {
182  return 1;
183  } else {
184  *dest++ = c;
185  }
186  c = 32;
187  } else {
188  *dest++ = c;
189  }
190  }
191  return 0;
192 }
193 
194 
195 static int fn_take_apart ( const char *name, char *base_name, char *ext_name, int jokery )
196 {
197  char *p = strchr(name, '.');
198  return p ? (
199  p == name || !p[1] ||
200  fn_part(base_name, name, p - name, 8, jokery) ||
201  fn_part(ext_name, p + 1, strlen(p + 1), 3, jokery)
202  ) : (
203  fn_part(base_name, name, strlen(name), 8, jokery) ||
204  fn_part(ext_name, NULL, 0, 3, jokery)
205  );
206 }
207 
208 
209 static int pattern_match ( void )
210 {
211  for (int a = 0; a < 8 + 3; a++)
212  if (ff.pattern[a] != '?' && ff.found[a] != ff.pattern[a])
213  return 1;
214  return 0;
215 }
216 
217 
218 
219 int cpmfs_search_file ( void )
220 {
221  ff.result_is_valid = 0;
222  if (ff.stop_search) {
223  DEBUGPRINT("FCB: FIND: stop_search condition!" NL);
224  return -1;
225  }
226  DIR *dir = drives[ff.drive].dir;
227  if (!dir) {
228  DEBUGPRINT("FCB: FIND: drive %c directory is not open?!" NL, ff.drive);
229  ff.stop_search = 1;
230  return -1;
231  }
232  for (;;) {
233  struct dirent *entry = readdir(dir);
234  if (!entry) {
235  DEBUGPRINT("FCB: FIND: entry NULL returned, end of directory maybe?" NL);
236  ff.stop_search = 1;
237  return -1;
238  }
239  if (fn_take_apart(entry->d_name, ff.found, ff.found + 8, 0)) {
240  DEBUGPRINT("FCB: FIND: ruling out filename \"%s\"" NL, entry->d_name);
241  continue;
242  }
243  ff.found[8 + 3] = 0; // FIXME: just for debug, to be able to print out!
244  DEBUGPRINT("FCB: FIND: considering formatted filename \"%s\"" NL, ff.found);
245  if (pattern_match()) {
246  DEBUGPRINT("FCB: FIND: no pattern match for this file" NL);
247  continue;
248  }
249  // stat file, store full path etc
250  strcpy(ff.host_name, entry->d_name);
251  strcpy(ff.host_path, drives[ff.drive].dir_path);
252  strcat(ff.host_path, entry->d_name);
253  DEBUGPRINT("FCB: FIND: trying to stat file: %s" NL, ff.host_path);
254  if (stat(ff.host_path, &ff.st)) {
255  DEBUGPRINT("FCB: FIND: cannot stat() file" NL);
256  continue;
257  }
258  if ((ff.st.st_mode & S_IFMT) != S_IFREG) {
259  DEBUGPRINT("FCB: FIND: skipping file, not a regular one!" NL);
260  continue;
261  }
262  DEBUGPRINT("FCB: FIND: cool, file is accepted!" NL);
263  // Also, if there was no joker characters, there cannot be more results, so close our directory
264  if (!(ff.options & CPMFS_SF_JOKERY))
265  ff.stop_search = 1;
266  ff.result_is_valid = 1;
267  // creating directory entry in DMA if it was requested, or something like that :-O
268  if ((ff.options & CPMFS_SF_STORE_IN_DMA0)) {
269  Uint8 *res = memory + cpm_dma + 32 * (ff.options & 3);
270  memset(memory + cpm_dma, 0, 0x80); // just to be sure, clear the whole current DMA area
271  memcpy(res + 1, ff.found, 11); // copy the file name into the desired 32-byte slice of the DMA
272  res[12] = 1; // TODO: trying what they will do ...
273  res[13] = 1;
274  res[14] = 1;
275  res[15] = 1;
276  return ff.options & 3;
277  } else
278  return 0;
279  }
280 }
281 
282 
283 // !! if CPMFS_SF_INPUT_IS_FCB is set, "drive" is ignored, and "input" is treated as a memory pointer to a CP/M search FCB
284 // !! if it is NOT specified, drive selects the drive (as-is, no "current drive" notion) and "input" is C-string style variable (with dot-notion etc)
285 int cpmfs_search_file_setup ( int drive, const Uint8 *input, int options )
286 {
287  ff.result_is_valid = 0;
288  ff.stop_search = 1;
289  ff.options = options;
290  if ((options & CPMFS_SF_INPUT_IS_FCB)) {
291  for (int a = 0; a < 8 + 3; a++) {
292  Uint8 c = input[a + 1] & 0x7F; // FIXME: we ignore the highest bits stuffs ...
293  if (!(options & CPMFS_SF_JOKERY) && c == '?')
294  return 1; // wildcard (joker[y]) is not allowed if not requested ...
295  if (c >= 'a' && c <= 'z')
296  c = c - 'a' + 'A'; // in FCB, it should be capital case already, maybe this is not needed, but who knows
297  ff.pattern[a] = c;
298  }
299  drive = input[0] & 0x7F;
300  drive = drive ? drive - 1 : current_drive;
301  } else {
302  // Input is NOT an FCB, but a C-string (null terminated etc), with dot notion, so we must convert it first
303  FATAL("%s(): non-FCB input is not implemented yet", __func__); // FIXME / TODO
304  if (fn_take_apart((const char*)input, ff.pattern, ff.pattern + 8, (options & CPMFS_SF_JOKERY)))
305  return 1;
306  }
307  if (drive < 0 || drive > 26 || !drives[drive].dir)
308  return 1;
309 #if 0
310  DIR *dir = opendir(drives[drive].dir_path);
311  if (dir) {
312  if (drives[drive].dir)
313  closedir(drives[drive].dir);
314  drives[drive].dir = dir;
315  } else
316 #endif
317  rewinddir(drives[drive].dir);
318  ff.drive = drive;
319  ff.stop_search = 0;
320  return 0;
321 }
result_is_valid
int result_is_valid
Definition: cpmfs.c:39
fd
int fd
Definition: cpmfs.c:51
cpmfs.h
emutools.h
bdos.h
pattern
char pattern[8+3+1]
Definition: cpmfs.c:35
cpm_dma
int cpm_dma
Definition: bdos.c:27
dir_path
char dir_path[PATH_MAX]
Definition: cpmfs.c:47
cpmfs_search_file_get_result_path
char * cpmfs_search_file_get_result_path(void)
Definition: cpmfs.c:154
cpmfs_close_all_files
void cpmfs_close_all_files(void)
Definition: cpmfs.c:82
Uint8
uint8_t Uint8
Definition: fat32.c:51
MAX_OPEN_FILES
#define MAX_OPEN_FILES
Definition: cpmfs.c:32
cpmfs_uninit
void cpmfs_uninit(void)
Definition: cpmfs.c:91
DEBUGPRINT
#define DEBUGPRINT(...)
Definition: emutools_basicdefs.h:171
ro
int ro
Definition: cpmfs.c:48
CPMFS_SF_JOKERY
#define CPMFS_SF_JOKERY
Definition: cpmfs.h:29
options
int options
Definition: cpmfs.c:41
conprintf
#define conprintf(...)
Definition: console.h:28
dir
DIR * dir
Definition: cpmfs.c:46
cpmfs_search_file
int cpmfs_search_file(void)
Definition: cpmfs.c:219
NL
#define NL
Definition: fat32.c:37
memory
Uint8 memory[0x100000]
Definition: commodore_65.c:43
host_name
char host_name[13]
Definition: cpmfs.c:37
hardware.h
host_path
char host_path[PATH_MAX]
Definition: cpmfs.c:38
cpmfs_mount_drive
int cpmfs_mount_drive(int drive, const char *dir_path, int dirbase_part_only)
Definition: cpmfs.c:101
last_fcb_addr
Uint16 last_fcb_addr
Definition: cpmfs.c:54
checksum
Uint8 checksum[0x10]
Definition: cpmfs.c:52
current_drive
int current_drive
Definition: cpmfs.c:58
Uint16
uint16_t Uint16
Definition: fat32.c:50
cpmfs_search_file_setup
int cpmfs_search_file_setup(int drive, const Uint8 *input, int options)
Definition: cpmfs.c:285
found
char found[8+3+1]
Definition: cpmfs.c:36
CPMFS_SF_STORE_IN_DMA0
#define CPMFS_SF_STORE_IN_DMA0
Definition: cpmfs.h:24
name
const char * name
Definition: joystick.c:46
CPMFS_SF_INPUT_IS_FCB
#define CPMFS_SF_INPUT_IS_FCB
Definition: cpmfs.h:30
sequence
int sequence
Definition: cpmfs.c:55
FATAL
#define FATAL(...)
Definition: xep128.h:117
cpmfs_init
void cpmfs_init(void)
Definition: cpmfs.c:67
drive
int drive
Definition: cpmfs.c:42
console.h
stop_search
int stop_search
Definition: cpmfs.c:40
st
struct stat st
Definition: cpmfs.c:43
DIRSEP_CHR
#define DIRSEP_CHR
Definition: emutools_basicdefs.h:142