diff --git a/xtrn/DDUploadProcessor/FILE_ID.DIZ b/xtrn/DDUploadProcessor/FILE_ID.DIZ
deleted file mode 100644
index 3f8d0408fd33b8e1a0189362687be72d8227f801..0000000000000000000000000000000000000000
--- a/xtrn/DDUploadProcessor/FILE_ID.DIZ
+++ /dev/null
@@ -1,9 +0,0 @@
-nhc  Digital Distortion Upload Processor yvw1.00
-hy             For Synchronet 3.14+
-k���������������������������������������������
-ncThis is an upload processor for Synchronet
-that allows for performing a virus scan on
-files inside of archives.  File extraction
-commands and the virus scan command are
-configurable.
-bhRelease date: 2009-12-29
\ No newline at end of file
diff --git a/xtrn/DDUploadProcessor/DDUP.cfg b/xtrn/dd_upload_processor/ddup.cfg
similarity index 100%
rename from xtrn/DDUploadProcessor/DDUP.cfg
rename to xtrn/dd_upload_processor/ddup.cfg
diff --git a/xtrn/DDUploadProcessor/DDUP.js b/xtrn/dd_upload_processor/ddup.js
similarity index 71%
rename from xtrn/DDUploadProcessor/DDUP.js
rename to xtrn/dd_upload_processor/ddup.js
index 96d1fd63371e2b1a2f6615820536441e27f401d2..e4c2231a7df5bdc36dc523c182a0c7c63667253b 100644
--- a/xtrn/DDUploadProcessor/DDUP.js
+++ b/xtrn/dd_upload_processor/ddup.js
@@ -9,11 +9,13 @@
  * BBS: Digital Distortion
  * BBS address: digdist.bbsindex.com
  *
- * Date       User              Description
+ * Date           Author               Description
  * 2009-12-25-
  * 2009-12-28 Eric Oulashin     Initial development
  * 2009-12-29 Eric Oulashin     Version 1.00
- *                              Initial public release
+ *                                            Initial public release
+ * 2022-06-08 Eric Oulashin     Version 1.01
+ *                                            Made fixes to get the scanner functionality working properly in Linux
  */
 
 /* Command-line arguments:
@@ -22,6 +24,13 @@
 
 load("sbbsdefs.js");
 
+// Require version 3.17 or newer of Synchronet (for file_chmod())
+if (system.version_num < 31700)
+{
+	console.print("\1nDigital Distortion Upload Processor requires Synchronet 3.17 or newer.\r\n");
+	exit(1);
+}
+
 // Determine the script's execution directory.
 // This code is a trick that was created by Deuce, suggested by Rob
 // Swindell as a way to detect which directory the script was executed
@@ -31,11 +40,14 @@ var gStartupPath = '.';
 try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; }
 gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,''));
 
-load(gStartupPath + "DDUP_Cleanup.js");
+load(gStartupPath + "ddup_cleanup.js");
 
 // Version information
-var gDDUPVersion = "1.00";
-var gDDUPVerDate = "2009-12-29";
+var gDDUPVersion = "1.01";
+var gDDUPVerDate = "2022-06-08";
+
+// Store whether or not this is running in Windows
+var gRunningInWindows = /^WIN/.test(system.platform.toUpperCase());
 
 
 // If the filename was specified on the command line, then use that
@@ -44,39 +56,39 @@ var gDDUPVerDate = "2009-12-29";
 var gFileToScan = "";
 if (argv.length > 0)
 {
-   if (typeof(argv[0]) == "string")
-   {
-      // Make sure the arguments are correct (in case they have spaces),
-      // then use the first one.
-      var fixedArgs = fixArgs(argv);
-      if ((typeof(fixedArgs[0]) == "string") && (fixedArgs[0].length > 0))
-         gFileToScan = fixedArgs[0];
-      else
-      {
-         console.print("nyhError: ncBlank filename argument given.\r\np");
-         exit(-2);
-      }
-   }
-   else
-   {
-      console.print("nyhError: ncUnknown command-line argument specified.\r\np");
-      exit(-1);
-   }
+	if (typeof(argv[0]) == "string")
+	{
+		// Make sure the arguments are correct (in case they have spaces),
+		// then use the first one.
+		var fixedArgs = fixArgs(argv);
+		if ((typeof(fixedArgs[0]) == "string") && (fixedArgs[0].length > 0))
+			gFileToScan = fixedArgs[0];
+		else
+		{
+			console.print("nyhError: ncBlank filename argument given.\r\np");
+			exit(-2);
+		}
+	}
+	else
+	{
+		console.print("nyhError: ncUnknown command-line argument specified.\r\np");
+		exit(-1);
+	}
 }
 else
 {
-   // Read the filename from DDArcViewerFilename.txt in the node directory.
-   // This is a workaround for file/directory names with spaces in
-   // them, which would get separated into separate command-line
-   // arguments for JavaScript scripts.
-   var filenameFileFilename = system.node_dir + "DDArcViewerFilename.txt";
-   var filenameFile = new File(filenameFileFilename);
-   if (filenameFile.open("r"))
-   {
-      if (!filenameFile.eof)
-         gFileToScan = filenameFile.readln(2048);
-      filenameFile.close();
-   }
+	// Read the filename from DDArcViewerFilename.txt in the node directory.
+	// This is a workaround for file/directory names with spaces in
+	// them, which would get separated into separate command-line
+	// arguments for JavaScript scripts.
+	var filenameFileFilename = system.node_dir + "DDArcViewerFilename.txt";
+	var filenameFile = new File(filenameFileFilename);
+	if (filenameFile.open("r"))
+	{
+		if (!filenameFile.eof)
+			gFileToScan = filenameFile.readln(2048);
+		filenameFile.close();
+	}
 }
 
 // Make sure the slashes in the filename are correct for the platform.
@@ -92,26 +104,27 @@ if (gFileToScan.length == 0)
 }
 
 // Create the global configuration objects.
-var gGenCfg = new Object();
-gGenCfg.scanCmd = "";
-gGenCfg.skipScanIfSysop = false;
-gGenCfg.pauseAtEnd = false;
-var gFileTypeCfg = new Object();
+var gGenCfg = {
+	scanCmd: "",
+	skipScanIfSysop: false,
+	pauseAtEnd: false
+};
+var gFileTypeCfg = {};
 
 // Read the configuration files to populate the global configuration object.
 var configFileRead = ReadConfigFile(gStartupPath);
 // If the configuration files weren't read, then output an error and exit.
 if (!configFileRead)
 {
-   console.print("nyhError: ncUpload processor is unable to read its\r\n");
-   console.print("configuration files.\r\np");
-   exit(2);
+	console.print("nyhError: ncUpload processor is unable to read its\r\n");
+	console.print("configuration files.\r\np");
+	exit(2);
 }
 // Exit if there is no scan command.
 if (gGenCfg.scanCmd.length == 0)
 {
-   console.print("nyhWarning: ncNo scan command configured for the upload processor.\r\n");
-   exit(0);
+	console.print("nyhWarning: ncNo scan command configured for the upload processor.\r\n");
+	exit(0);
 }
 
 // Global variables
@@ -224,150 +237,154 @@ function fixArgs(input)
 //               non-zero means failure.
 function processFile(pFilename)
 {
-   // Display the program header stuff - The name of the file being scanned
-   // and the status header line
-   var justFilename = getFilenameFromPath(pFilename);
-   console.print("nwhScanning b" + justFilename.substr(0, 70));
-   console.print("n\r\nb7                             File Scan Status                                  n\r\n");
-
-   // If the skipScanIfSysop option is enabled and the user is a sysop,
-   // then assume the file is good.
-   if (gGenCfg.skipScanIfSysop && user.compare_ars("SYSOP"))
-   {
-      printf(gStatusPrintfStr, "gh", "Auto-approving the file (you're a sysop)");
-      console.print(gOKStrWithNewline);
-      return 0;
-   }
-
-   var retval = 0;
-
-   // Look for the file extension in gFileTypeCfg to get the file scan settings.
-   // If the file extension is not there, then go ahead and scan it (to be on the
-   // safe side).
-   var filenameExtension = getFilenameExtension(pFilename);
-   if (typeof(gFileTypeCfg[filenameExtension]) != "undefined")
-   {
-      if (gFileTypeCfg[filenameExtension].scanOption == "scan")
-      {
-         // - If the file has an extract command, then:
-         //   Extract the file to a temporary directory in the node dir
-         //   For each file in the directory:
-         //     If it's a subdir
-         //        Recurse into it
-         //     else
-         //        Scan it for viruses
-         //        If non-zero retval
-         //           Return with error code
-         var filespec = pFilename;
-         if (gFileTypeCfg[filenameExtension].extractCmd.length > 0)
-         {
-            // Create the base work directory for this script in the node dir.
-            // And just in case that dir already exists, remove it before
-            // creating it.
-            var baseWorkDir = system.node_dir + "DDUploadProcessor_Temp";
-            deltree(baseWorkDir + "/");
-            if (!mkdir(baseWorkDir))
-            {
-               console.print("nyhWarning: nwh Unable to create the work dir.n\r\n");
-               retval = -1;
-            }
-            
-            // If all is okay, then create the directory in the temporary work dir.
-            var workDir = baseWorkDir + "/" + justFilename + "_temp";
-            if (retval == 0)
-            {
-               deltree(workDir + "/");
-               if (!mkdir(workDir))
-               {
-                  console.print("nyhWarning: nwh Unable to create a dir in the temporary work dir.n\r\n");
-                  retval = -1;
-               }
-            }
-
-            // If all is okay, we can now process the file.
-            if (retval == 0)
-            {
-               // Extract the file to the work directory
-               printf(gStatusPrintfStr, "mh", "Extracting the file...");
-               var errorStr = extractFileToDir(pFilename, workDir);
-               if (errorStr.length == 0)
-               {
-                  console.print(gOKStrWithNewline);
-                  // Scan the files in the work directory.
-                  printf(gStatusPrintfStr, "r", "Scanning files inside the archive for viruses...");
-                  var retObj = scanFilesInDir(workDir);
-                  retval = retObj.returnCode;
-                  if (retObj.returnCode == 0)
-                     console.print(gOKStrWithNewline);
-                  else
-                  {
-                     console.print(gFailStrWithNewline);
-                     console.print("nyhVirus scan failed.  Scan output:n\r\n");
-                     for (var index = 0; index < retObj.cmdOutput.length; ++index)
-                     {
-                        console.print(retObj.cmdOutput[index]);
-                        console.crlf();
-                     }
-                  }
-               }
-               else
-               {
-                  console.print(gFailStrWithNewline);
-                  // Scan the files in the work directory.
-                  console.print("nyhWarning: nwh Unable to extract to work dir.n\r\n");
-                  retval = -2;
-               }
-            }
-            // Remove the work directory.
-            deltree(baseWorkDir + "/");
-         }
-         else
-         {
-            // The file has no extract command, so just scan it.
-            printf(gStatusPrintfStr, "bh", "Scanning...");
-            var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pFilename) + "\"");
-            // Run the scan command and capture its output, in case the scan fails.
-            var retObj = runExternalCmdWithOutput(scanCmd);
-            retval = retObj.returnCode;
-            if (retObj.returnCode == 0)
-               console.print(gOKStrWithNewline);
-            else
-            {
-               console.print(gFailStrWithNewline);
-               console.print("nyhVirus scan failed.  Scan output:n\r\n");
-               for (var index = 0; index < retObj.cmdOutput.length; ++index)
-               {
-                  console.print(retObj.cmdOutput[index]);
-                  console.crlf();
-               }
-            }
-         }
-      }
-      else if (gFileTypeCfg[filenameExtension].scanOption == "always fail")
-         exitCode = 10;
-   }
-   else
-   {
-      // There's nothing configured for the file's extension, so just scan it.
-      printf(gStatusPrintfStr, "r", "Scanning...");
-      var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pFilename) + "\"");
-      var retObj = runExternalCmdWithOutput(scanCmd);
-      retval = retObj.returnCode;
-      if (retObj.returnCode == 0)
-         console.print(gOKStrWithNewline);
-      else
-      {
-         console.print(gFailStrWithNewline);
-         console.print("nyhVirus scan failed.  Scan output:n\r\n");
-         for (var index = 0; index < retObj.cmdOutput.length; ++index)
-         {
-            console.print(retObj.cmdOutput[index]);
-            console.crlf();
-         }
-      }
-   }
-
-   return retval;
+	// Display the program header stuff - The name of the file being scanned
+	// and the status header line
+	var justFilename = getFilenameFromPath(pFilename);
+	console.print("nwhScanning b" + justFilename.substr(0, 70));
+	console.print("n\r\nb7                             File Scan Status                                  n\r\n");
+
+	// If the skipScanIfSysop option is enabled and the user is a sysop,
+	// then assume the file is good.
+	if (gGenCfg.skipScanIfSysop && user.compare_ars("SYSOP"))
+	{
+		printf(gStatusPrintfStr, "gh", "Auto-approving the file (you're a sysop)");
+		console.print(gOKStrWithNewline);
+		return 0;
+	}
+
+	var retval = 0;
+
+	// Look for the file extension in gFileTypeCfg to get the file scan settings.
+	// If the file extension is not there, then go ahead and scan it (to be on the
+	// safe side).
+	var filenameExtension = getFilenameExtension(pFilename);
+	if (typeof(gFileTypeCfg[filenameExtension]) != "undefined")
+	{
+		if (gFileTypeCfg[filenameExtension].scanOption == "scan")
+		{
+			// - If the file has an extract command, then:
+			//   Extract the file to a temporary directory in the node dir
+			//   For each file in the directory:
+			//     If it's a subdir
+			//        Recurse into it
+			//     else
+			//        Scan it for viruses
+			//        If non-zero retval
+			//           Return with error code
+			var filespec = pFilename;
+			if (gFileTypeCfg[filenameExtension].extractCmd.length > 0)
+			{
+				// Create the base work directory for this script in the node dir.
+				// And just in case that dir already exists, remove it before
+				// creating it.
+				var baseWorkDir = system.node_dir + "DDUploadProcessor_Temp";
+				deltree(baseWorkDir + "/");
+				if (!mkdir(baseWorkDir))
+				{
+					console.print("nyhWarning: nwh Unable to create the work dir.n\r\n");
+					retval = -1;
+				}
+				file_chmod(baseWorkDir, 0x1fd); // Octal 775, rwxrwxr-x
+				//chmodDirsRecursive(baseWorkDir, 0x1fd); // Octal 775, rwxrwxr-x
+				
+				// If all is okay, then create the directory in the temporary work dir.
+				var workDir = baseWorkDir + "/" + justFilename + "_temp";
+				if (retval == 0)
+				{
+					deltree(workDir + "/");
+					if (!mkdir(workDir))
+					{
+						console.print("nyhWarning: nwh Unable to create a dir in the temporary work dir.n\r\n");
+						retval = -1;
+					}
+				}
+
+				// If all is okay, we can now process the file.
+				if (retval == 0)
+				{
+					// Extract the file to the work directory
+					printf(gStatusPrintfStr, "mh", "Extracting the file...");
+					var errorStr = extractFileToDir(pFilename, workDir);
+					if (errorStr.length == 0)
+					{
+						// In case we're running in Linux, chmod all directories in the work dir recursively so the scanner can access their files
+						chmodDirsRecursive(workDir, 0x1fd); // Octal 775, rwxrwxr-x
+						console.print(gOKStrWithNewline);
+						// Scan the files in the work directory.
+						printf(gStatusPrintfStr, "r", "Scanning files inside the archive for viruses...");
+						var retObj = scanFilesInDir(workDir);
+						retval = retObj.returnCode;
+						if (retObj.returnCode == 0)
+							console.print(gOKStrWithNewline);
+						else
+						{
+							console.print(gFailStrWithNewline);
+							console.print("nyhVirus scan failed.(1)  Scan output:n\r\n");
+							for (var index = 0; index < retObj.cmdOutput.length; ++index)
+							{
+								console.print(retObj.cmdOutput[index]);
+								console.crlf();
+							}
+						}
+					}
+					else
+					{
+						console.print(gFailStrWithNewline);
+						// Scan the files in the work directory.
+						console.print("nyhWarning: nwh Unable to extract to work dir.n\r\n");
+						retval = -2;
+					}
+				}
+				// Remove the work directory.
+				deltree(baseWorkDir + "/");
+			}
+			else
+			{
+				// The file has no extract command, so just scan it.
+				printf(gStatusPrintfStr, "bh", "Scanning...");
+				var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pFilename) + "\"");
+				// Run the scan command and capture its output, in case the scan fails.
+				var retObj = runExternalCmdWithOutput(scanCmd);
+				retval = retObj.returnCode;
+				if (retObj.returnCode == 0)
+					console.print(gOKStrWithNewline);
+				else
+				{
+					console.print(gFailStrWithNewline);
+					console.print("nyhVirus scan failed.(2)  Scan output:n\r\n");
+					for (var index = 0; index < retObj.cmdOutput.length; ++index)
+					{
+						console.print(retObj.cmdOutput[index]);
+						console.crlf();
+					}
+				}
+			}
+		}
+		else if (gFileTypeCfg[filenameExtension].scanOption == "always fail")
+			exitCode = 10;
+	}
+	else
+	{
+		// There's nothing configured for the file's extension, so just scan it.
+		printf(gStatusPrintfStr, "r", "Scanning...");
+		var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pFilename) + "\"");
+		var retObj = runExternalCmdWithOutput(scanCmd);
+		retval = retObj.returnCode;
+		if (retObj.returnCode == 0)
+			console.print(gOKStrWithNewline);
+		else
+		{
+			console.print(gFailStrWithNewline);
+			console.print("nyhVirus scan failed.(3)  Scan output:n\r\n");
+			for (var index = 0; index < retObj.cmdOutput.length; ++index)
+			{
+				console.print(retObj.cmdOutput[index]);
+				console.crlf();
+			}
+		}
+	}
+
+	return retval;
 }
 
 // Recursively scans the files in a directory using the scan command in
@@ -481,7 +498,7 @@ function ReadConfigFile(pCfgFilePath)
 {
    // Read the file type settings.
    var fileTypeSettingsRead = false;
-   var fileTypeCfgFile = new File(pCfgFilePath + "DDUPFileTypes.cfg");
+   var fileTypeCfgFile = new File(pCfgFilePath + "ddup_file_types.cfg");
    if (fileTypeCfgFile.open("r"))
    {
       if (fileTypeCfgFile.length > 0)
@@ -566,7 +583,7 @@ function ReadConfigFile(pCfgFilePath)
 
    // Read the general program configuration
    var genSettingsRead = false;
-   var genCfgFile = new File(pCfgFilePath + "DDUP.cfg");
+   var genCfgFile = new File(pCfgFilePath + "ddup.cfg");
    if (genCfgFile.open("r"))
    {
       if (genCfgFile.length > 0)
@@ -728,11 +745,7 @@ function getPathFromFilename(pFilename)
    if (pFilename.length == 0)
       return "";
 
-   // Determine which slash character to use for paths, depending
-   // on the OS.
-   if (getPathFromFilename.inWin == undefined)
-      getPathFromFilename.inWin = /^WIN/.test(system.platform.toUpperCase());
-   var pathSlash = (getPathFromFilename.inWin ? "\\" : "/");
+   var pathSlash = (gRunningInWindows ? "\\" : "/");
 
    // Make sure the filename has the correct slashes for
    // the platform.
@@ -783,15 +796,10 @@ function fixPathSlashes(pPath)
    if (pPath.length == 0)
       return "";
 
-   // Create a variable to store whether or not we're in Windows,
-   // but only once (for speed).
-   if (fixPathSlashes.inWin == undefined)
-      fixPathSlashes.inWin = /^WIN/.test(system.platform.toUpperCase());
-
    // Fix the slashes and return the fixed version.
-   //return(fixPathSlashes.inWin ? pPath.replace("/", "\\") : pPath.replace("\\", "/"));
+   //return(gRunningInWindows ? pPath.replace("/", "\\") : pPath.replace("\\", "/"));
    var path = pPath;
-   if (fixPathSlashes.inWin) // Windows
+   if (gRunningInWindows) // Windows
    {
       while (path.indexOf("/") > -1)
          path = path.replace("/", "\\");
@@ -956,60 +964,76 @@ function execCmdWithOutput(pCommand)
 //               cmdOutput: An array of strings containing the program's output.
 function runExternalCmdWithOutput(pCommand)
 {
-   // Determine whether or not we're in Windows.
-   if (runExternalCmdWithOutput.inWin == undefined)
-      runExternalCmdWithOutput.inWin = /^WIN/.test(system.platform.toUpperCase());
-
-   var retObj = null; // The return object
-   var wroteScriptFile = false; // Whether or not we were able to write the script file
-
-   // In the node directory, write a batch file (if in Windows) or a *nix shell
-   // script (if not in Windows) containing the command to run.
-   var scriptFilename = "";
-   if (runExternalCmdWithOutput.inWin)
-   {
-      // Write a Windows batch file to run the command
-      scriptFilename = fixPathSlashes(system.node_dir + "DDUP_ScanCmd.bat");
-      //console.print(":" + scriptFilename + ":\r\n\1p"); // Temporary (for debugging)
-      var scriptFile = new File(scriptFilename);
-      if (scriptFile.open("w"))
-      {
-         scriptFile.writeln("@echo off");
-         scriptFile.writeln(pCommand);
-         scriptFile.close();
-         wroteScriptFile = true;
-         retObj = execCmdWithOutput(scriptFilename);
-      }
-   }
-   else
-   {
-      // Write a *nix shell script to run the command
-      scriptFilename = system.node_dir + "DDUP_ScanCmd.sh";
-      var scriptFile = new File(scriptFilename);
-      if (scriptFile.open("w"))
-      {
-         scriptFile.writeln("#!/bin/bash"); // Hopefully /bin/bash is valid on the system!
-         scriptFile.writeln(pCommand);
-         scriptFile.close();
-         wroteScriptFile = true;
-         system.exec("chmod ugo+x " + scriptFilename);
-         retObj = execCmdWithOutput("bash " + scriptFilename);
-      }
-   }
-
-   // Remove the script file, if it exists
-   if (file_exists(scriptFilename))
-      file_remove(scriptFilename);
-
-   // If we were unable to write the script file, then create retObj with
-   // a returnCode indicating failure.
-   if (!wroteScriptFile)
-   {
-      // Could not open the script file for writing
-      retObj = new Object();
-      retObj.cmdOutput = new Array();
-      retObj.returnCode = -1;
-   }
+	var retObj = null; // The return object
+	var wroteScriptFile = false; // Whether or not we were able to write the script file
+
+	// In the node directory, write a batch file (if in Windows) or a *nix shell
+	// script (if not in Windows) containing the command to run.
+	var scriptFilename = "";
+	if (gRunningInWindows)
+	{
+		// Write a Windows batch file to run the command
+		scriptFilename = fixPathSlashes(system.node_dir + "DDUP_ScanCmd.bat");
+		//console.print(":" + scriptFilename + ":\r\n\1p"); // Temporary (for debugging)
+		var scriptFile = new File(scriptFilename);
+		if (scriptFile.open("w"))
+		{
+			scriptFile.writeln("@echo off");
+			scriptFile.writeln(pCommand);
+			scriptFile.close();
+			wroteScriptFile = true;
+			retObj = execCmdWithOutput(scriptFilename);
+		}
+	}
+	else
+	{
+		// Write a *nix shell script to run the command
+		scriptFilename = system.node_dir + "DDUP_ScanCmd.sh";
+		var scriptFile = new File(scriptFilename);
+		if (scriptFile.open("w"))
+		{
+			scriptFile.writeln("#!/bin/bash"); // Hopefully /bin/bash is valid on the system!
+			scriptFile.writeln(pCommand);
+			scriptFile.close();
+			wroteScriptFile = true;
+			file_chmod(scriptFilename, 775); // rwxrwxr-x
+			retObj = execCmdWithOutput("bash " + scriptFilename);
+		}
+	}
+
+	// Remove the script file, if it exists
+	if (file_exists(scriptFilename))
+		file_remove(scriptFilename);
+
+	// If we were unable to write the script file, then create retObj with
+	// a returnCode indicating failure.
+	if (!wroteScriptFile)
+	{
+		// Could not open the script file for writing
+		retObj = {
+			cmdOutput: [],
+			returnCode: -1
+		};
+	}
+
+	return retObj;
+}
 
-   return retObj;
+// Changes the mode value of a directory and all of its subdirectories recursively
+//
+// Parameters:
+//  pBaseDir: The directory to chmod recursively (along with all of its subdirectories)
+//  pMode: The mode value (number) to apply
+function chmodDirsRecursive(pBaseDir, pMode)
+{
+	if (typeof(pBaseDir) !== "string" || !file_isdir(pBaseDir) || typeof(pMode) !== "number")
+		return;
+
+	file_chmod(pBaseDir, pMode);
+	var fileEntries = directory(backslash(pBaseDir) + "*");
+	for (var i = 0; i < fileEntries.length; ++i)
+	{
+		if (file_isdir(fileEntries[i]))
+			chmodDirsRecursive(fileEntries[i], pMode);
+	}
 }
\ No newline at end of file
diff --git a/xtrn/DDUploadProcessor/DDUP_Cleanup.js b/xtrn/dd_upload_processor/ddup_cleanup.js
similarity index 100%
rename from xtrn/DDUploadProcessor/DDUP_Cleanup.js
rename to xtrn/dd_upload_processor/ddup_cleanup.js
diff --git a/xtrn/DDUploadProcessor/DDUPFileTypes.cfg b/xtrn/dd_upload_processor/ddup_file_types.cfg
similarity index 100%
rename from xtrn/DDUploadProcessor/DDUPFileTypes.cfg
rename to xtrn/dd_upload_processor/ddup_file_types.cfg
diff --git a/xtrn/DDUploadProcessor/Read Me.txt b/xtrn/dd_upload_processor/readme.txt
similarity index 92%
rename from xtrn/DDUploadProcessor/Read Me.txt
rename to xtrn/dd_upload_processor/readme.txt
index 7ae3b2cce93b2e4d466068f75b8795ec1a15590c..6d56a494b53e1777e489b6394712a289837f30f9 100644
--- a/xtrn/DDUploadProcessor/Read Me.txt	
+++ b/xtrn/dd_upload_processor/readme.txt
@@ -1,13 +1,12 @@
                    Digital Distortion Upload Processor
-                              Version 1.00
-                        Release date: 2009-12-29
+                              Version 1.01
+                        Release date: 2022-06-08
 
                                   by
 
                              Eric Oulashin
                      Sysop of Digital Distortion BBS
-               BBS internet address: digitaldistortionbbs.com
-                                     digdist.bbsindex.com
+                 BBS internet address: digdist.bbsindex.com
                      Email: eric.oulashin@gmail.com
 
 
@@ -22,6 +21,7 @@ Contents
 4. Installation and Setup
 5. Main configuration file
 6. Archive file type configuration file
+7. Revision History
 
 
 1. Disclaimer
@@ -90,19 +90,17 @@ EXTRACT command lines set properly for your system.  For information on
 that configuration file, see section 6: Archive file type configuration
 file.
 
-The following archive contains Win32 command-line archivers for popular
-archive file formats:
-http://digdist.bbsindex.com/miscFilesForDL/Win32CmdLineCompressionTools.zip
-The archivers included in that archive handle the most popular file formats
-(ZIP, 7Z (7-Zip), RAR, ARJ, TAR, GZ, TGZ, and TAR.GZ), and they are set up in
-DDUPFileTypes.cfg to extract popular file formats (ZIP, 7Z (7-Zip), RAR, ARJ,
-MSI, TAR, GZ, TGZ, and TAR.GZ).  Note that you will need to edit that .cfg
-file and change the path to the .exe file according to where you copied them
-on your system.  If your BBS is running in Windows, the included configuration
-file should work for you (although it does also have the Linux command lines
-as comments).  If you copy the archivers to a directory that is not in your
-system path, you will need to edit the DDUPFileTypes.cfg file to include the
-full paths with the archive executables.
+Win32 command-line archivers have been included with this script for
+convenience.  Those archivers have been set up in DDUPFileTypes.cfg
+to extract popular file formats (ZIP, 7Z (7-Zip), RAR, ARJ, MSI, TAR,
+GZ, TGZ, and TAR.GZ).  The Win32 archivers are in the Win32Archivers
+directory.  So if your BBS is running in Windows, the included
+configuration file should work for you (although it does also have the
+Linux command lines as comments).  You will need to copy the files from
+the Win32Archivers directory to a directory in your path or another
+directory of your choice.  If you copy them to a directory that is not
+in your path, you will need to edit the DDUPFileTypes.cfg file to include
+the full paths with the archive executables.
 
 Extractor notes:
 DDUPFileTypes.cfg includes a setup for using 7-Zip to extract ISO (CD/DVD
@@ -340,4 +338,11 @@ Using the above example configuration for zip files, if the user (on node 1)
 uploads D:\Files\someArchive.zip and your Synchronet installation is located
 in D:\sbbs, the temp directory is D:\sbbs\node1\DDUploadProcessor_Temp, and
 the extract command will be translated to the following:
-unzip.exe -qq -o D:\Files\someArchive.zip -d D:\sbbs\node1\DDUploadProcessor_Temp
\ No newline at end of file
+unzip.exe -qq -o D:\Files\someArchive.zip -d D:\sbbs\node1\DDUploadProcessor_Temp
+
+
+7. Revision History
+===================
+Version  Date         Description
+-------  ----         -----------
+1.00     2009-12-29   First general public release
\ No newline at end of file
diff --git a/xtrn/DDUploadProcessor/Revision history.txt b/xtrn/dd_upload_processor/version_history.txt
similarity index 75%
rename from xtrn/DDUploadProcessor/Revision history.txt
rename to xtrn/dd_upload_processor/version_history.txt
index 6bb5abf43dceb394e57abb1c5bceb17fcdc0c2fd..75b89dbd7c65bbb4a86a0361c3199b317cd18560 100644
--- a/xtrn/DDUploadProcessor/Revision history.txt	
+++ b/xtrn/dd_upload_processor/version_history.txt
@@ -2,4 +2,5 @@ Revision History for Digital Distortion Upload Processor
 ========================================================
 Version  Date         Description
 -------  ----         -----------
+1.01     2022-06-08   Updated so that the scanning works properly in Linux
 1.00     2009-12-29   First general public release
\ No newline at end of file