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_mount_common.h"
22 #include "program_mount.h"
23 
24 #include "dosbox.h"
25 
26 #include "bios_disk.h"
27 #include "control.h"
28 #include "drives.h"
29 #include "fs_utils.h"
30 #include "shell.h"
31 #include "string_utils.h"
32 #include "../ints/int10.h"
33 
Move_Z(char new_z)34 void MOUNT::Move_Z(char new_z)
35 {
36 	const char new_drive_z = toupper(new_z);
37 
38 	if (new_drive_z < 'A' || new_drive_z > 'Z') {
39 		WriteOut(MSG_Get("PROGRAM_MOUNT_DRIVEID_ERROR"), new_drive_z);
40 		return;
41 	}
42 
43 	const uint8_t new_idx = drive_index(new_drive_z);
44 
45 	if (Drives[new_idx]) {
46 		WriteOut(MSG_Get("PROGRAM_MOUNT_MOVE_Z_ERROR_1"), new_drive_z);
47 		return;
48 	}
49 
50 	if (new_idx < DOS_DRIVES - 1) {
51 		ZDRIVE_NUM = new_idx;
52 		/* remap drives */
53 		Drives[new_idx] = Drives[25];
54 		Drives[25] = 0;
55 		if (!first_shell) return; //Should not be possible
56 		/* Update environment */
57 		std::string line = "";
58 		std::string tempenv = {new_drive_z, ':', '\\'};
59 		if (first_shell->GetEnvStr("PATH",line)) {
60 			std::string::size_type idx = line.find('=');
61 			std::string value = line.substr(idx +1 , std::string::npos);
62 			while ( (idx = value.find("Z:\\")) != std::string::npos ||
63 				(idx = value.find("z:\\")) != std::string::npos  )
64 				value.replace(idx,3,tempenv);
65 			line = std::move(value);
66 		}
67 		if (!line.size()) line = tempenv;
68 		first_shell->SetEnv("PATH",line.c_str());
69 		tempenv += "COMMAND.COM";
70 		first_shell->SetEnv("COMSPEC",tempenv.c_str());
71 
72 		/* Update batch file if running from Z: (very likely: autoexec) */
73 		if (first_shell->bf) {
74 			std::string &name = first_shell->bf->filename;
75 			if (starts_with("Z:", name))
76 				name[0] = new_drive_z;
77 		}
78 		/* Change the active drive */
79 		if (DOS_GetDefaultDrive() == 25)
80 			DOS_SetDrive(new_idx);
81 	}
82 }
83 
ListMounts()84 void MOUNT::ListMounts()
85 {
86 	const std::string header_drive = MSG_Get("PROGRAM_MOUNT_STATUS_DRIVE");
87 	const std::string header_type = MSG_Get("PROGRAM_MOUNT_STATUS_TYPE");
88 	const std::string header_label = MSG_Get("PROGRAM_MOUNT_STATUS_LABEL");
89 
90 	const int term_width = real_readw(BIOSMEM_SEG, BIOSMEM_NB_COLS);
91 	const auto width_1 = static_cast<int>(header_drive.size());
92 	const auto width_3 = std::max(11, static_cast<int>(header_label.size()));
93 	const auto width_2 = term_width - 3 - width_1 - width_3;
94 
95 	auto print_row = [&](const std::string &txt_1,
96 							const std::string &txt_2,
97 							const std::string &txt_3) {
98 		WriteOut("%-*s %-*s %-*s\n",
99 					width_1, txt_1.c_str(),
100 					width_2, txt_2.c_str(),
101 					width_3, txt_3.c_str());
102 	};
103 
104 	WriteOut(MSG_Get("PROGRAM_MOUNT_STATUS_1"));
105 	print_row(header_drive, header_type, header_label);
106 	for (int i = 0; i < term_width; i++)
107 		WriteOut_NoParsing("-");
108 
109 	for (uint8_t d = 0; d < DOS_DRIVES; d++) {
110 		if (Drives[d]) {
111 			print_row(std::string{drive_letter(d)},
112 						Drives[d]->GetInfo(),
113 						To_Label(Drives[d]->GetLabel()));
114 		}
115 	}
116 }
117 
Run(void)118 void MOUNT::Run(void) {
119 	DOS_Drive * newdrive;char drive;
120 	std::string label;
121 	std::string umount;
122 	std::string newz;
123 
124 	//Hack To allow long commandlines
125 	ChangeToLongCmd();
126 	/* Parse the command line */
127 	/* if the command line is empty show current mounts */
128 	if (!cmd->GetCount()) {
129 		ListMounts();
130 		return;
131 	}
132 
133 	/* In secure mode don't allow people to change mount points.
134 		* Neither mount nor unmount */
135 	if (control->SecureMode()) {
136 		WriteOut(MSG_Get("PROGRAM_CONFIG_SECURE_DISALLOW"));
137 		return;
138 	}
139 	bool path_relative_to_last_config = false;
140 	if (cmd->FindExist("-pr",true)) path_relative_to_last_config = true;
141 
142 	/* Check for unmounting */
143 	if (cmd->FindString("-u",umount,false)) {
144 		WriteOut(UnmountHelper(umount[0]), toupper(umount[0]));
145 		return;
146 	}
147 
148 	/* Check for moving Z: */
149 	/* Only allowing moving it once. It is merely a convenience added for the wine team */
150 	if (ZDRIVE_NUM == 25 && cmd->FindString("-z", newz,false)) {
151 		Move_Z(newz[0]);
152 		return;
153 	}
154 
155 	if (cmd->FindExist("-cd", false)) {
156 		WriteOut(MSG_Get("PROGRAM_MOUNT_NO_OPTION"), "-cd");
157 		return;
158 	}
159 
160 	std::string type="dir";
161 	cmd->FindString("-t",type,true);
162 	bool iscdrom = (type =="cdrom"); //Used for mscdex bug cdrom label name emulation
163 	if (type=="floppy" || type=="dir" || type=="cdrom" || type =="overlay") {
164 		Bit16u sizes[4] ={0};
165 		Bit8u mediaid;
166 		std::string str_size = "";
167 		if (type=="floppy") {
168 			str_size="512,1,2880,2880";/* All space free */
169 			mediaid=0xF0;		/* Floppy 1.44 media */
170 		} else if (type=="dir" || type == "overlay") {
171 			// 512*32*32765==~500MB total size
172 			// 512*32*16000==~250MB total free size
173 			str_size="512,32,32765,16000";
174 			mediaid=0xF8;		/* Hard Disk */
175 		} else if (type=="cdrom") {
176 			str_size="2048,1,65535,0";
177 			mediaid=0xF8;		/* Hard Disk */
178 		} else {
179 			WriteOut(MSG_Get("PROGAM_MOUNT_ILL_TYPE"),type.c_str());
180 			return;
181 		}
182 		/* Parse the free space in mb's (kb's for floppies) */
183 		std::string mb_size;
184 		if (cmd->FindString("-freesize",mb_size,true)) {
185 			char teststr[1024];
186 			Bit16u freesize = static_cast<Bit16u>(atoi(mb_size.c_str()));
187 			if (type=="floppy") {
188 				// freesize in kb
189 				sprintf(teststr,"512,1,2880,%d",freesize*1024/(512*1));
190 			} else {
191 				Bit32u total_size_cyl=32765;
192 				Bit32u free_size_cyl=(Bit32u)freesize*1024*1024/(512*32);
193 				if (free_size_cyl>65534) free_size_cyl=65534;
194 				if (total_size_cyl<free_size_cyl) total_size_cyl=free_size_cyl+10;
195 				if (total_size_cyl>65534) total_size_cyl=65534;
196 				sprintf(teststr,"512,32,%d,%d",total_size_cyl,free_size_cyl);
197 			}
198 			str_size=teststr;
199 		}
200 
201 		cmd->FindString("-size",str_size,true);
202 		char number[21] = { 0 };const char * scan = str_size.c_str();
203 		Bitu index = 0;Bitu count = 0;
204 		/* Parse the str_size string */
205 		while (*scan && index < 20 && count < 4) {
206 			if (*scan==',') {
207 				number[index] = 0;
208 				sizes[count++] = atoi(number);
209 				index = 0;
210 			} else number[index++] = *scan;
211 			scan++;
212 		}
213 		if (count < 4) {
214 			number[index] = 0; //always goes correct as index is max 20 at this point.
215 			sizes[count] = atoi(number);
216 		}
217 
218 		// get the drive letter
219 		cmd->FindCommand(1,temp_line);
220 		if ((temp_line.size() > 2) || ((temp_line.size() > 1) && (temp_line[1]!=':'))) goto showusage;
221 		const int i_drive = toupper(temp_line[0]);
222 
223 		if (i_drive < 'A' || i_drive > 'Z') {
224 			goto showusage;
225 		}
226 		drive = int_to_char(i_drive);
227 		if (type == "overlay") {
228 			//Ensure that the base drive exists:
229 			if (!Drives[drive_index(drive)]) {
230 				WriteOut(MSG_Get("PROGRAM_MOUNT_OVERLAY_NO_BASE"));
231 				return;
232 			}
233 		} else if (Drives[drive_index(drive)]) {
234 			WriteOut(MSG_Get("PROGRAM_MOUNT_ALREADY_MOUNTED"),
235 						drive,
236 						Drives[drive_index(drive)]->GetInfo());
237 			return;
238 		}
239 
240 		if (!cmd->FindCommand(2,temp_line)) {
241 			goto showusage;
242 		}
243 		if (!temp_line.size()) {
244 			goto showusage;
245 		}
246 		if (path_relative_to_last_config && control->configfiles.size() && !Cross::IsPathAbsolute(temp_line)) {
247 			std::string lastconfigdir(control->configfiles[control->configfiles.size() - 1]);
248 			std::string::size_type pos = lastconfigdir.rfind(CROSS_FILESPLIT);
249 			if (pos == std::string::npos) {
250 				pos = 0; //No directory then erase string
251 			}
252 			lastconfigdir.erase(pos);
253 			if (lastconfigdir.length()) {
254 				temp_line = lastconfigdir + CROSS_FILESPLIT + temp_line;
255 			}
256 		}
257 
258 #if defined(WIN32)
259 		/* Removing trailing backslash if not root dir so stat
260 			* will succeed */
261 		if (temp_line.size() > 3 && temp_line.back() == '\\')
262 			temp_line.pop_back();
263 #endif
264 
265 		const std::string real_path = to_native_path(temp_line);
266 		if (real_path.empty()) {
267 			LOG_MSG("MOUNT: Path '%s' not found", temp_line.c_str());
268 		} else {
269 			std::string home_resolve = temp_line;
270 			Cross::ResolveHomedir(home_resolve);
271 			if (home_resolve == real_path) {
272 				LOG_MSG("MOUNT: Path '%s' found",
273 						temp_line.c_str());
274 			} else {
275 				LOG_MSG("MOUNT: Path '%s' found, while looking for '%s'",
276 						real_path.c_str(),
277 						temp_line.c_str());
278 			}
279 			temp_line = real_path;
280 		}
281 
282 		struct stat test;
283 		if (stat(temp_line.c_str(),&test)) {
284 			WriteOut(MSG_Get("PROGRAM_MOUNT_ERROR_1"),temp_line.c_str());
285 			return;
286 		}
287 		/* Not a switch so a normal directory/file */
288 		if (!S_ISDIR(test.st_mode)) {
289 			WriteOut(MSG_Get("PROGRAM_MOUNT_ERROR_2"),temp_line.c_str());
290 			return;
291 		}
292 
293 		if (temp_line[temp_line.size() - 1] != CROSS_FILESPLIT) temp_line += CROSS_FILESPLIT;
294 		Bit8u bit8size = (Bit8u)sizes[1];
295 
296 		if (type == "cdrom") {
297 			// Following options were relevant only for physical CD-ROM support:
298 			for (auto opt : {"-usecd", "-noioctl", "-ioctl", "-ioctl_dx", "-ioctl_mci", "-ioctl_dio"}) {
299 				if (cmd->FindExist(opt, false))
300 					WriteOut(MSG_Get("MSCDEX_WARNING_NO_OPTION"), opt);
301 			}
302 
303 			int error = 0;
304 			newdrive  = new cdromDrive(drive,temp_line.c_str(),sizes[0],bit8size,sizes[2],0,mediaid,error);
305 			// Check Mscdex, if it worked out...
306 			switch (error) {
307 				case 0  :	WriteOut(MSG_Get("MSCDEX_SUCCESS"));				break;
308 				case 1  :	WriteOut(MSG_Get("MSCDEX_ERROR_MULTIPLE_CDROMS"));	break;
309 				case 2  :	WriteOut(MSG_Get("MSCDEX_ERROR_NOT_SUPPORTED"));	break;
310 				case 3  :	WriteOut(MSG_Get("MSCDEX_ERROR_PATH"));				break;
311 				case 4  :	WriteOut(MSG_Get("MSCDEX_TOO_MANY_DRIVES"));		break;
312 				case 5  :	WriteOut(MSG_Get("MSCDEX_LIMITED_SUPPORT"));		break;
313 				default :	WriteOut(MSG_Get("MSCDEX_UNKNOWN_ERROR"));			break;
314 			};
315 			if (error && error!=5) {
316 				delete newdrive;
317 				return;
318 			}
319 		} else {
320 			/* Give a warning when mount c:\ or the / */
321 #if defined (WIN32)
322 			if ( (temp_line == "c:\\") || (temp_line == "C:\\") ||
323 				(temp_line == "c:/") || (temp_line == "C:/")    )
324 				WriteOut(MSG_Get("PROGRAM_MOUNT_WARNING_WIN"));
325 #else
326 			if (temp_line == "/") WriteOut(MSG_Get("PROGRAM_MOUNT_WARNING_OTHER"));
327 #endif
328 			if (type == "overlay") {
329 				localDrive *ldp = dynamic_cast<localDrive *>(
330 						Drives[drive_index(drive)]);
331 				cdromDrive *cdp = dynamic_cast<cdromDrive *>(
332 						Drives[drive_index(drive)]);
333 				if (!ldp || cdp) {
334 					WriteOut(MSG_Get("PROGRAM_MOUNT_OVERLAY_INCOMPAT_BASE"));
335 					return;
336 				}
337 				std::string base = ldp->GetBasedir();
338 				Bit8u o_error = 0;
339 				newdrive = new Overlay_Drive(base.c_str(),temp_line.c_str(),sizes[0],bit8size,sizes[2],sizes[3],mediaid,o_error);
340 				//Erase old drive on success
341 				if (o_error) {
342 					if (o_error == 1) WriteOut("No mixing of relative and absolute paths. Overlay failed.");
343 					else if (o_error == 2) WriteOut("overlay directory can not be the same as underlying file system.");
344 					else WriteOut("Something went wrong");
345 					delete newdrive;
346 					return;
347 				}
348 				//Copy current directory if not marked as deleted.
349 				if (newdrive->TestDir(ldp->curdir)) {
350 					safe_strcpy(newdrive->curdir,
351 								ldp->curdir);
352 				}
353 
354 				delete Drives[drive_index(drive)];
355 				Drives[drive_index(drive)] = nullptr;
356 			} else {
357 				newdrive = new localDrive(temp_line.c_str(),sizes[0],bit8size,sizes[2],sizes[3],mediaid);
358 			}
359 		}
360 	} else {
361 		WriteOut(MSG_Get("PROGRAM_MOUNT_ILL_TYPE"),type.c_str());
362 		return;
363 	}
364 	Drives[drive_index(drive)] = newdrive;
365 	/* Set the correct media byte in the table */
366 	mem_writeb(Real2Phys(dos.tables.mediaid) + (drive_index(drive)) * 9,
367 				newdrive->GetMediaByte());
368 	if (type != "overlay")
369 		WriteOut(MSG_Get("PROGRAM_MOUNT_STATUS_2"), drive,
370 					newdrive->GetInfo());
371 	else
372 		WriteOut(MSG_Get("PROGRAM_MOUNT_OVERLAY_STATUS"),
373 					temp_line.c_str(), drive);
374 	/* check if volume label is given and don't allow it to updated in the future */
375 	if (cmd->FindString("-label",label,true)) newdrive->dirCache.SetLabel(label.c_str(),iscdrom,false);
376 	/* For hard drives set the label to DRIVELETTER_Drive.
377 		* For floppy drives set the label to DRIVELETTER_Floppy.
378 		* This way every drive except cdroms should get a label.*/
379 	else if (type == "dir" || type == "overlay") {
380 		label = drive; label += "_DRIVE";
381 		newdrive->dirCache.SetLabel(label.c_str(),iscdrom,false);
382 	} else if (type == "floppy") {
383 		label = drive; label += "_FLOPPY";
384 		newdrive->dirCache.SetLabel(label.c_str(),iscdrom,true);
385 	}
386 	if (type == "floppy") incrementFDD();
387 	return;
388 showusage:
389 	WriteOut(MSG_Get("SHELL_CMD_MOUNT_HELP_LONG"));
390 	return;
391 }
392 
MOUNT_ProgramStart(Program ** make)393 void MOUNT_ProgramStart(Program **make) {
394 	*make=new MOUNT;
395 }
396