1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 *
4 * Copyright (C) 2002-2021 The DOSBox Team
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21 #include "program_imgmount.h"
22
23 #include "dosbox.h"
24
25 #include <vector>
26
27 #include "bios_disk.h"
28 #include "control.h"
29 #include "cross.h"
30 #include "drives.h"
31 #include "fs_utils.h"
32 #include "ide.h"
33 #include "mapper.h"
34 #include "program_mount_common.h"
35 #include "shell.h"
36 #include "string_utils.h"
37 #include "../ints/int10.h"
38
Run(void)39 void IMGMOUNT::Run(void) {
40 //Hack To allow long commandlines
41 ChangeToLongCmd();
42
43 // Usage
44 if (!cmd->GetCount() || cmd->FindExist("/?", false) ||
45 cmd->FindExist("-h", false) || cmd->FindExist("--help", false)) {
46 WriteOut(MSG_Get("SHELL_CMD_IMGMOUNT_HELP_LONG"), PRIMARY_MOD_NAME);
47 return;
48 }
49
50 /* In secure mode don't allow people to change imgmount points.
51 * Neither mount nor unmount */
52 if (control->SecureMode()) {
53 WriteOut(MSG_Get("PROGRAM_CONFIG_SECURE_DISALLOW"));
54 return;
55 }
56
57 char drive;
58 std::vector<std::string> paths;
59 std::string umount;
60 /* Check for unmounting */
61 if (cmd->FindString("-u",umount,false)) {
62 WriteOut(UnmountHelper(umount[0]), toupper(umount[0]));
63 return;
64 }
65
66
67 std::string type = "hdd";
68 std::string fstype = "fat";
69 cmd->FindString("-t",type,true);
70 cmd->FindString("-fs",fstype,true);
71
72 // Types 'cdrom' and 'iso' are synonyms. Name 'cdrom' is easier
73 // to remember and makes more sense, while name 'iso' is
74 // required for backwards compatibility and for users conflating
75 // -fs and -t parameters.
76 if (type == "cdrom")
77 type = "iso";
78
79 if (type != "floppy" && type != "hdd" && type != "iso") {
80 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_TYPE_UNSUPPORTED"),
81 type.c_str());
82 return;
83 }
84
85 Bit16u sizes[4] = {0};
86 bool imgsizedetect = false;
87
88 std::string str_size = "";
89 Bit8u mediaid = 0xF8;
90
91 // Possibly used to hold the IDE channel and drive slot for CDROM types
92 std::string ide_value = {};
93 int8_t ide_index = -1;
94 bool is_second_cable_slot = false;
95 const bool wants_ide = cmd->FindString("-ide", ide_value, true) || cmd->FindExist("-ide", true);
96
97
98 if (type == "floppy") {
99 mediaid = 0xF0;
100 } else if (type == "iso") {
101 // str_size="2048,1,65535,0"; // ignored, see drive_iso.cpp
102 // (AllocationInfo)
103 mediaid = 0xF8;
104 fstype = "iso";
105
106 if (wants_ide) {
107 IDE_Get_Next_Cable_Slot(ide_index, is_second_cable_slot);
108 }
109 }
110
111 cmd->FindString("-size",str_size,true);
112 if ((type=="hdd") && (str_size.size()==0)) {
113 imgsizedetect = true;
114 } else {
115 char number[21] = { 0 };const char * scan = str_size.c_str();
116 Bitu index = 0;Bitu count = 0;
117 /* Parse the str_size string */
118 while (*scan && index < 20 && count < 4) {
119 if (*scan==',') {
120 number[index] = 0;
121 sizes[count++] = atoi(number);
122 index = 0;
123 } else number[index++] = *scan;
124 scan++;
125 }
126 if (count < 4) {
127 number[index] = 0; //always goes correct as index is max 20 at this point.
128 sizes[count] = atoi(number);
129 }
130 }
131
132 if (fstype=="fat" || fstype=="iso") {
133 // get the drive letter
134 if (!cmd->FindCommand(1,temp_line) || (temp_line.size() > 2) || ((temp_line.size() > 1) && (temp_line[1]!=':'))) {
135 WriteOut_NoParsing(MSG_Get("PROGRAM_IMGMOUNT_SPECIFY_DRIVE"));
136 return;
137 }
138 const int i_drive = toupper(temp_line[0]);
139 if (i_drive < 'A' || i_drive > 'Z') {
140 WriteOut_NoParsing(MSG_Get("PROGRAM_IMGMOUNT_SPECIFY_DRIVE"));
141 return;
142 }
143 drive = int_to_char(i_drive);
144 } else if (fstype=="none") {
145 cmd->FindCommand(1,temp_line);
146 if ((temp_line.size() > 1) || (!isdigit(temp_line[0]))) {
147 WriteOut_NoParsing(MSG_Get("PROGRAM_IMGMOUNT_SPECIFY2"));
148 return;
149 }
150 drive = temp_line[0];
151 if ((drive<'0') || (drive>=(MAX_DISK_IMAGES+'0'))) {
152 WriteOut_NoParsing(MSG_Get("PROGRAM_IMGMOUNT_SPECIFY2"));
153 return;
154 }
155 } else {
156 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_FORMAT_UNSUPPORTED"),fstype.c_str());
157 return;
158 }
159
160 // find all file parameters, assuming that all option parameters have been removed
161 while (cmd->FindCommand((unsigned int)(paths.size() + 2), temp_line) && temp_line.size()) {
162 // Try to find the path on native filesystem first
163 const std::string real_path = to_native_path(temp_line);
164 if (real_path.empty()) {
165 LOG_MSG("IMGMOUNT: Path '%s' not found, maybe it's a DOS path",
166 temp_line.c_str());
167 } else {
168 std::string home_resolve = temp_line;
169 Cross::ResolveHomedir(home_resolve);
170 if (home_resolve == real_path) {
171 LOG_MSG("IMGMOUNT: Path '%s' found",
172 temp_line.c_str());
173 } else {
174 LOG_MSG("IMGMOUNT: Path '%s' found, while looking for '%s'",
175 real_path.c_str(),
176 temp_line.c_str());
177 }
178 temp_line = real_path;
179 }
180
181 // Test if input is file on virtual DOS drive.
182 struct stat test;
183 if (stat(temp_line.c_str(),&test)) {
184 //See if it works if the ~ are written out
185 std::string homedir(temp_line);
186 Cross::ResolveHomedir(homedir);
187 if (!stat(homedir.c_str(),&test)) {
188 temp_line = std::move(homedir);
189 } else {
190 // convert dosbox filename to system filename
191 char fullname[CROSS_LEN];
192 char tmp[CROSS_LEN];
193 safe_strcpy(tmp, temp_line.c_str());
194
195 Bit8u dummy;
196 if (!DOS_MakeName(tmp, fullname, &dummy) || strncmp(Drives[dummy]->GetInfo(),"local directory",15)) {
197 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_NON_LOCAL_DRIVE"));
198 return;
199 }
200
201 localDrive *ldp = dynamic_cast<localDrive*>(Drives[dummy]);
202 if (ldp==NULL) {
203 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_FILE_NOT_FOUND"));
204 return;
205 }
206 ldp->GetSystemFilename(tmp, fullname);
207 temp_line = tmp;
208
209 if (stat(temp_line.c_str(),&test)) {
210 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_FILE_NOT_FOUND"));
211 return;
212 }
213
214 LOG_MSG("IMGMOUNT: Path '%s' found on virtual drive %c:",
215 fullname, drive_letter(dummy));
216 }
217 }
218 if (S_ISDIR(test.st_mode)) {
219 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_MOUNT"));
220 return;
221 }
222 paths.push_back(temp_line);
223 }
224 if (paths.size() == 0) {
225 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_SPECIFY_FILE"));
226 return;
227 }
228 if (paths.size() == 1)
229 temp_line = paths[0];
230
231 if (fstype=="fat") {
232 if (imgsizedetect) {
233 FILE * diskfile = fopen_wrap(temp_line.c_str(), "rb+");
234 if (!diskfile) {
235 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_INVALID_IMAGE"));
236 return;
237 }
238 fseek(diskfile, 0L, SEEK_END);
239 Bit32u fcsize = (Bit32u)(ftell(diskfile) / 512L);
240 Bit8u buf[512];
241 fseek(diskfile, 0L, SEEK_SET);
242 if (fread(buf,sizeof(Bit8u),512,diskfile)<512) {
243 fclose(diskfile);
244 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_INVALID_IMAGE"));
245 return;
246 }
247 fclose(diskfile);
248 if ((buf[510]!=0x55) || (buf[511]!=0xaa)) {
249 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_INVALID_GEOMETRY"));
250 return;
251 }
252 Bitu sectors=(Bitu)(fcsize/(16*63));
253 if (sectors*16*63!=fcsize) {
254 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_INVALID_GEOMETRY"));
255 return;
256 }
257 sizes[0]=512; sizes[1]=63; sizes[2]=16; sizes[3]=sectors;
258
259 LOG_MSG("autosized image file: %d:%d:%d:%d",sizes[0],sizes[1],sizes[2],sizes[3]);
260 }
261
262 if (Drives[drive_index(drive)]) {
263 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_ALREADY_MOUNTED"));
264 return;
265 }
266
267 std::vector<DOS_Drive*> imgDisks;
268 std::vector<std::string>::size_type i;
269 std::vector<DOS_Drive*>::size_type ct;
270
271 for (i = 0; i < paths.size(); i++) {
272 std::unique_ptr<fatDrive> newDrive(
273 new fatDrive(paths[i].c_str(),sizes[0],sizes[1],sizes[2],sizes[3],0));
274
275 if (newDrive->created_successfully) {
276 imgDisks.push_back(static_cast<DOS_Drive*>(newDrive.release()));
277 } else {
278 // Tear-down all prior drives when we hit a problem
279 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_CANT_CREATE"));
280 for (auto pImgDisk : imgDisks) {
281 delete pImgDisk;
282 }
283 return;
284 }
285 }
286
287 // Update DriveManager
288 for (ct = 0; ct < imgDisks.size(); ct++) {
289 DriveManager::AppendDisk(drive_index(drive),
290 imgDisks[ct]);
291 }
292 DriveManager::InitializeDrive(drive_index(drive));
293
294 // Set the correct media byte in the table
295 mem_writeb(Real2Phys(dos.tables.mediaid) +
296 drive_index(drive) * 9,
297 mediaid);
298
299 /* Command uses dta so set it to our internal dta */
300 RealPt save_dta = dos.dta();
301 dos.dta(dos.tables.tempdta);
302
303 for (ct = 0; ct < imgDisks.size(); ct++) {
304 const bool notify = (ct == (imgDisks.size() - 1));
305 DriveManager::CycleDisks(drive_index(drive), notify);
306 char root[7] = {drive,':','\\','*','.','*',0};
307 DOS_FindFirst(root, DOS_ATTR_VOLUME); // force obtaining the label and saving it in dirCache
308 }
309 dos.dta(save_dta);
310
311 std::string tmp(paths[0]);
312 for (i = 1; i < paths.size(); i++) {
313 tmp += "; " + paths[i];
314 }
315 WriteOut(MSG_Get("PROGRAM_MOUNT_STATUS_2"), drive, tmp.c_str());
316
317 if (paths.size() == 1) {
318 auto *newdrive = static_cast<fatDrive*>(imgDisks[0]);
319 if (('A' <= drive && drive <= 'B' && !(newdrive->loadedDisk->hardDrive)) ||
320 ('C' <= drive && drive <= 'D' && newdrive->loadedDisk->hardDrive)) {
321 const size_t idx = drive_index(drive);
322 imageDiskList[idx] = newdrive->loadedDisk;
323 updateDPT();
324 }
325 }
326 } else if (fstype=="iso") {
327 if (Drives[drive_index(drive)]) {
328 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_ALREADY_MOUNTED"));
329 return;
330 }
331
332 // create new drives for all images
333 std::vector<DOS_Drive*> isoDisks;
334 std::vector<std::string>::size_type i;
335 std::vector<DOS_Drive*>::size_type ct;
336 for (i = 0; i < paths.size(); i++) {
337 int error = -1;
338 DOS_Drive* newDrive = new isoDrive(drive, paths[i].c_str(), mediaid, error);
339 isoDisks.push_back(newDrive);
340 switch (error) {
341 case 0 : break;
342 case 1 : WriteOut(MSG_Get("MSCDEX_ERROR_MULTIPLE_CDROMS")); break;
343 case 2 : WriteOut(MSG_Get("MSCDEX_ERROR_NOT_SUPPORTED")); break;
344 case 3 : WriteOut(MSG_Get("MSCDEX_ERROR_OPEN")); break;
345 case 4 : WriteOut(MSG_Get("MSCDEX_TOO_MANY_DRIVES")); break;
346 case 5 : WriteOut(MSG_Get("MSCDEX_LIMITED_SUPPORT")); break;
347 case 6 : WriteOut(MSG_Get("MSCDEX_INVALID_FILEFORMAT")); break;
348 default : WriteOut(MSG_Get("MSCDEX_UNKNOWN_ERROR")); break;
349 }
350 // error: clean up and leave
351 if (error) {
352 for (ct = 0; ct < isoDisks.size(); ct++) {
353 delete isoDisks[ct];
354 }
355 return;
356 }
357 }
358 // Update DriveManager
359 for (ct = 0; ct < isoDisks.size(); ct++) {
360 DriveManager::AppendDisk(drive_index(drive),
361 isoDisks[ct]);
362 }
363 DriveManager::InitializeDrive(drive_index(drive));
364
365 // Set the correct media byte in the table
366 mem_writeb(Real2Phys(dos.tables.mediaid) +
367 drive_index(drive) * 9,
368 mediaid);
369
370 // If instructed, attach to IDE controller as ATAPI CD-ROM device
371 if (wants_ide) {
372 if (ide_index >= 0) {
373 IDE_CDROM_Attach(ide_index, is_second_cable_slot, drive_index(drive));
374 } else {
375 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_IDE_CONTROLLERS_UNAVAILABLE"));
376 }
377 }
378
379 // Print status message (success)
380 WriteOut(MSG_Get("MSCDEX_SUCCESS"));
381 std::string tmp(paths[0]);
382 for (i = 1; i < paths.size(); i++) {
383 tmp += "; " + paths[i];
384 }
385 WriteOut(MSG_Get("PROGRAM_MOUNT_STATUS_2"), drive, tmp.c_str());
386
387 } else if (fstype == "none") {
388 FILE *newDisk = fopen_wrap(temp_line.c_str(), "rb+");
389 if (!newDisk) {
390 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_INVALID_IMAGE"));
391 return;
392 }
393 fseek(newDisk,0L, SEEK_END);
394 Bit32u imagesize = (ftell(newDisk) / 1024);
395 const bool hdd = (imagesize > 2880);
396 //Seems to make sense to require a valid geometry..
397 if (hdd && sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0 && sizes[3] == 0) {
398 fclose(newDisk);
399 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_SPECIFY_GEOMETRY"));
400 return;
401 }
402
403 imageDisk * newImage = new imageDisk(newDisk, temp_line.c_str(), imagesize, hdd);
404
405 if (hdd) newImage->Set_Geometry(sizes[2],sizes[3],sizes[1],sizes[0]);
406 imageDiskList[drive - '0'].reset(newImage);
407 updateDPT();
408 WriteOut(MSG_Get("PROGRAM_IMGMOUNT_MOUNT_NUMBER"),drive - '0',temp_line.c_str());
409 }
410
411 // check if volume label is given. be careful for cdrom
412 //if (cmd->FindString("-label",label,true)) newdrive->dirCache.SetLabel(label.c_str());
413 return;
414 }
415
IMGMOUNT_ProgramStart(Program ** make)416 void IMGMOUNT_ProgramStart(Program **make) {
417 *make=new IMGMOUNT;
418 }
419