From 695b533e6697d0c98447f6999a6a2c27690151a6 Mon Sep 17 00:00:00 2001
From: Robert Sprowson <rsprowson@gitlab.riscosopen.org>
Date: Thu, 6 Jun 2024 15:41:20 +0100
Subject: [PATCH] Add path manipulation commands AppPath, RemPath, PrepPath

This triplet of commands allows obey files and similar to 'edit' path variables
such that they don't grow ever longer each time the obey file is run, and to
ensure it is possible to get the priority of elements on the path as desired.

Removed a malloc/free from *SafeLogon; the other commands all assume one OSCLI
buffer of space is available on the stack and use an automatic variable.
---
 Resources/UK/CmdHelp  | Bin 2224 -> 2665 bytes
 Resources/UK/Messages |   1 +
 c/main                | 154 +++++++++++++++++++++++++++++++++++++-----
 cmhg/header           |  30 ++++++++
 h/main                |  15 ++--
 5 files changed, 177 insertions(+), 23 deletions(-)

diff --git a/Resources/UK/CmdHelp b/Resources/UK/CmdHelp
index 38a105bf5e6399367c2e1c87e9954f48ec15dc2c..0f89259bba7213866e12d193e96858e5c0535c7b 100644
GIT binary patch
delta 455
zcmaKo!3u&v5Qe3Lkhh=*m~(WF5D~g{lzM_Grxe!RVO^!V_)0=g)mtRE8&c+BS@!4K
z;hVo*yq4|OZ+6THn(TO&25+Ig<|`XRRHM%EBq)Z6I}S)tLDIxtV39ou4nI6HlT1rM
z7}8*rOWeFQkSr_R<njt8BTazakqXj^MSey@r57ZcM(tcarRSL5&%J=VkpsiVE%aY;
k((pjVSba90^6QZ>>;2pZO@*rIr@xC<#dm|xjsKXv0Y1p0RR910

delta 12
TcmaDUvO#cz1>5G2>=jG^BEtmI

diff --git a/Resources/UK/Messages b/Resources/UK/Messages
index 73b3525..5936e4f 100644
--- a/Resources/UK/Messages
+++ b/Resources/UK/Messages
@@ -2,3 +2,4 @@
 NoFS:File server '%0' not found
 BadFile:Corrupt CMOS file
 BadVer:CMOS file is for a different OS version
+WantStr:System variable must contain a string
diff --git a/c/main b/c/main
index 6df64ce..f07755c 100644
--- a/c/main
+++ b/c/main
@@ -28,6 +28,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdbool.h>
+#include <ctype.h>
 #include "kernel.h"
 
 /*From exports*/
@@ -134,6 +135,22 @@ static int get_nvm_size(void)
    return rg.r[0]?osbyte_CONFIGURE_CHECKSUM+1:rg.r[1];
 }
 
+static int stricmp(const char *first, const char *second)
+
+{  for (;;)
+   {  unsigned int a = *(const unsigned char *) first++;
+      unsigned int b = *(const unsigned char *) second++;
+
+      if (a == 0) return -b;
+      if (a != b)
+      {  unsigned int c = (unsigned int) tolower(a);
+         unsigned int d = (unsigned int) tolower(b);
+         signed int result = c - d;
+         if (result != 0) return result;
+      }
+   }
+}
+
 /*------------------------------------------------------------------------*/
 static os_error *Add_App (const char *tail)
 
@@ -858,7 +875,7 @@ finish:
 /*------------------------------------------------------------------------*/
 static os_error *Safe_Logon (const char *tail)
 
-{  struct safelogon_args {char *fs, *user, *password; char argb [os_CLI_LIMIT_RO4 + 1];} *argl;
+{  struct {char *fs, *user, *password; char argb [os_CLI_LIMIT_RO4 + 1];} argl;
    int station, net, context, collate;
    fileswitch_fs_no temp_fs;
    netfs_file_server_context file_server_context;
@@ -868,10 +885,6 @@ static os_error *Safe_Logon (const char *tail)
 
    tracef ("Safe_Logon\n");
 
-   argl = malloc(sizeof(struct safelogon_args));
-   if (argl == 0)
-       goto finish;
-
    /*Fix bug: check that the temporary filing system is indeed NetFS. JRC
       16th Feb 1995*/
    if ((error = xosargs_read_temporary_fs (&temp_fs)) != NULL)
@@ -879,23 +892,23 @@ static os_error *Safe_Logon (const char *tail)
 
    if (temp_fs == fileswitch_FS_NUMBER_NETFS)
    {  if ((error = xos_read_args ("fs/a,user/a,password", tail,
-            (char *) argl, sizeof *argl, NULL)) != NULL)
+            (char *) &argl, sizeof argl, NULL)) != NULL)
          goto finish;
 
-      tracef ("fs \"%s\"\n" _ argl->fs);
-      tracef ("user \"%s\"\n" _ argl->user);
+      tracef ("fs \"%s\"\n" _ argl.fs);
+      tracef ("user \"%s\"\n" _ argl.user);
       tracef ("password \"%s\"\n" _
-            argl->password != NULL? argl->password: "NULL");
+            argl.password != NULL? argl.password: "NULL");
 
       /*If the first character of the fs is not ':', we steer clear.*/
-      if (argl->fs [0] == ':')
-      {  argl->fs++;
+      if (argl.fs [0] == ':')
+      {  argl.fs++;
 
          /*Station number or name?*/
-         named_fs = xeconet_read_station_number (argl->fs, NULL, &station,
+         named_fs = xeconet_read_station_number (argl.fs, NULL, &station,
                &net) != NULL;
          if (named_fs)
-            tracef ("fs is named \"%s\"\n" _ argl->fs);
+            tracef ("fs is named \"%s\"\n" _ argl.fs);
          else
             tracef ("fs is numbered %d.%d\n" _ net _ station);
 
@@ -920,7 +933,7 @@ static os_error *Safe_Logon (const char *tail)
                   file_server_context.user_name);
 
             if (named_fs)
-            {  if ((error = xterritory_collate (territory_CURRENT, argl->fs,
+            {  if ((error = xterritory_collate (territory_CURRENT, argl.fs,
                      file_server_context.disc_name, territory_IGNORE_CASE,
                      &collate)) != NULL)
                   goto finish;
@@ -932,7 +945,7 @@ static os_error *Safe_Logon (const char *tail)
                   file_server_context.net_no == net;
 
             if (found_fs)
-            {  if ((error = xterritory_collate (territory_CURRENT, argl->user,
+            {  if ((error = xterritory_collate (territory_CURRENT, argl.user,
                      file_server_context.user_name, territory_IGNORE_CASE,
                      &collate)) != NULL)
                   goto finish;
@@ -944,7 +957,7 @@ static os_error *Safe_Logon (const char *tail)
                      on? JRC 19th Dec 1994*/
                   netfs_read_user_info info;
 
-                  sprintf (info AS request.user_name, "%s\r", argl->user);
+                  sprintf (info AS request.user_name, "%s\r", argl.user);
 
                   tracef ("doing fs op ...\n");
                   if ((error = xnetfs_do_fs_op_to_given_fs
@@ -963,7 +976,6 @@ static os_error *Safe_Logon (const char *tail)
    }  }  }  }  }
 
 finish:
-   free(argl);
    /*Ignore errors up to this point. If there have been any, it's a safe bet
       that |logon_required| is TRUE.*/
    if (error != NULL)
@@ -981,6 +993,111 @@ finish:
    return error;
 }
 /*------------------------------------------------------------------------*/
+static os_error *AppPrepRem_Path (const char *tail, int which)
+
+{  struct {char *varname, *element; char argb [os_CLI_LIMIT_RO4 + 1];} argl;
+   os_error *error = NULL;
+   os_var_type type;
+   size_t existing, extra, len;
+   char *comma, *next, *editpath = NULL;
+
+   tracef ("App|Prep|RemPath subreason %d\n" _ which);
+
+   if ((error = xos_read_args ("v/a,e/a", tail, (char *) &argl,
+         sizeof argl, NULL)) != NULL)
+      goto finish;
+
+   /*Read the existing path variable, it could be any size*/
+   xos_read_var_val_size (argl.varname, 0, os_VARTYPE_STRING, (int *) &existing, NULL, &type);
+   if (existing == 0)
+   {  /*Be silent on remove of non-existent variables*/
+      if (which == main_REM_PATH) goto finish;
+      tracef ("Variable '%s' doesn't exist, acting as create\n" _ argl.varname);
+      which = main_APP_PATH;
+   }
+   else
+   {  if (type != os_VARTYPE_STRING)
+      {  error = main_error_lookup (ErrorBase_BootCommands + 3, "WantStr");
+         goto finish;
+      }
+      /*Length of contents of existing path variable*/
+      existing = ~existing;
+   }
+
+   /*For append or prepend, overallocate to allow space for the insertion*/
+   extra = (which != main_REM_PATH) ? (strlen (argl.element) + 1 /*Possible comma*/)
+                                    : 0;
+   editpath = (char *) malloc (existing + 1 /*Terminator*/ + extra);
+   if (editpath == NULL)
+   {  error = main_error_lookup (ErrorBase_BootCommands + 2, "NoMem");
+      goto finish;
+   }
+   if (existing)
+   {  error = xos_read_var_val (argl.varname, editpath, existing, 0, os_VARTYPE_STRING,
+                                NULL, NULL, NULL);
+      if (error != NULL) /*Where did it go?!*/
+         goto finish;
+   }
+   editpath [existing] = '\0';
+
+   /*Scan the existing path elements, deleting matches with the given element*/
+   next = editpath;
+   while (*next)
+   {  comma = strchr (next, ',');
+      if (comma) *comma = '\0'; /*Isolate that portion*/
+      len = strlen (next);
+      if (comma) len++; /*Comma counted as part of the element length*/
+      if (stricmp (next, argl.element) == 0)
+      {  /*Delete it*/
+         tracef ("  element '%s' matches subject, deleting\n" _ next);
+         if (!comma && (next > editpath))
+         {  /*Removing the last element, just trim with a terminator*/
+            next [-1] = '\0';
+            break;
+         }
+         memmove (next, &next [len], strlen (&next [len]) + 1 /*Terminator*/);
+      }
+      else
+      {  /*Step next*/
+         tracef ("  element '%s'\n" _ next);
+         next = next + len;
+         if (comma) *comma = ','; /*Restore the comma*/
+      }
+   }
+   tracef ("After removing duplicates '%s'\n" _ editpath);
+
+   if (which != main_REM_PATH)
+   {  /*Firm up on that possible comma now duplicates are removed*/
+      len = strlen (editpath);
+      if (len == 0) which = main_APP_PATH;
+
+      /*Append or prepend (any remove was done implicitly)*/
+      if (which == main_APP_PATH)
+      {  if (len) strcat (editpath, ",");
+         strcat (editpath, argl.element);
+      }
+      else
+      {  memmove (&editpath [extra], editpath, strlen (editpath));
+         strcpy (editpath, argl.element);
+         editpath [extra - 1] = ',';
+      }
+   }
+   tracef ("After edits '%s'\n" _ editpath);
+   error = xos_set_var_val (argl.varname, (byte *) editpath, strlen (editpath),
+                            0, os_VARTYPE_STRING, NULL, NULL);
+
+finish:
+   free (editpath);
+   tracef ("App|Prep|RemPath DONE\n");
+   if (error != NULL)
+      tracef ("with error %s\n" _ error->errmess);
+   return error;
+}
+
+static os_error *App_Path (const char *tail)  { return AppPrepRem_Path (tail, main_APP_PATH); }
+static os_error *Prep_Path (const char *tail) { return AppPrepRem_Path (tail, main_PREP_PATH); }
+static os_error *Rem_Path (const char *tail)  { return AppPrepRem_Path (tail, main_REM_PATH); }
+/*------------------------------------------------------------------------*/
 static os_error *Shrink_RMA(const char *tail)
 
 {
@@ -1097,6 +1214,9 @@ _kernel_oserror *main_initialise (char *tail, int podule_base,
    Commands [main_SAVE_CMOS]  = &Save_CMOS;
    Commands [main_REPEAT]     = &Repeat;
    Commands [main_SAFE_LOGON] = &Safe_Logon;
+   Commands [main_APP_PATH]   = &App_Path;
+   Commands [main_PREP_PATH]  = &Prep_Path;
+   Commands [main_REM_PATH]   = &Rem_Path;
    Commands [main_FREE_POOL]  = &Free_Pool;
    Commands [main_SHRINK_RMA] = &Shrink_RMA;
    Commands [main_ADD_TO_RMA] = &Add_To_RMA;
diff --git a/cmhg/header b/cmhg/header
index 3732200..6b741f4 100644
--- a/cmhg/header
+++ b/cmhg/header
@@ -68,6 +68,21 @@ command-keyword-table: main_command
       add-syntax:,
       invalid-syntax: "Syntax:	*SafeLogon [[:]<station number>|:<File server name>] <user name> [[:<CR>]<Password>]",
       help-text: "*SafeLogon initialises the current (or given) file server for your use, except that if you are already logged on, it does nothing\n"),
+   AppPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      add-syntax:,
+      invalid-syntax: "Syntax:	*AppPath <variable> <path element>",
+      help-text: "*AppPath appends a path element to a path variable, ensuring there are no duplicates\n"),
+   PrepPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      add-syntax:,
+      invalid-syntax: "Syntax:	*PrepPath <variable> <path element>",
+      help-text: "*PrepPath prepends a path element to a path variable, ensuring there are no duplicates\n"),
+   RemPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      add-syntax:,
+      invalid-syntax: "Syntax:	*RemPath <variable> <path element>",
+      help-text: "*RemPath removes a path element from a path variable\n"),
    FreePool( min-args: 0, max-args: 0,
       gstrans-map: 0,
       add-syntax:,
@@ -139,6 +154,21 @@ command-keyword-table: main_command
       international:,
       invalid-syntax: "SafeLogonSyntax",
       help-text: "SafeLogonHelp"),
+   AppPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      international:,
+      invalid-syntax: "AppPathSyntax",
+      help-text: "AppPathHelp"),
+   PrepPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      international:,
+      invalid-syntax: "PrepPathSyntax",
+      help-text: "PrepPathHelp"),
+   RemPath( min-args: 2, max-args: 2,
+      gstrans-map: 2,
+      international:,
+      invalid-syntax: "RemPathSyntax",
+      help-text: "RemPathHelp"),
    FreePool( min-args: 0, max-args: 0,
       gstrans-map: 0,
       international:,
diff --git a/h/main b/h/main
index 78fa957..711ec71 100644
--- a/h/main
+++ b/h/main
@@ -39,12 +39,15 @@
 #define main_SAVE_CMOS     5
 #define main_REPEAT        6
 #define main_SAFE_LOGON    7
-#define main_FREE_POOL     8
-#define main_SHRINK_RMA    9
-#define main_ADD_TO_RMA    10
-#define main_APP_SLOT      11
-#define main_X             12
-#define main_COMMAND_COUNT 13
+#define main_APP_PATH      8
+#define main_PREP_PATH     9
+#define main_REM_PATH      10
+#define main_FREE_POOL     11
+#define main_SHRINK_RMA    12
+#define main_ADD_TO_RMA    13
+#define main_APP_SLOT      14
+#define main_X             15
+#define main_COMMAND_COUNT 16
 
 #define X_ENVVAR           "X$Error"
 #define RES_APP_PATH       "Resources:$.Apps"
-- 
GitLab