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