patch 9.1.1050: too many strlen() calls in os_unix.c

Problem:  too many strlen() calls in os_unix.c
Solution: refactor os_unix.c and remove calls to strlen()
          (John Marriott)

closes: #16496

Signed-off-by: John Marriott <basilisk@internode.on.net>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/os_unix.c b/src/os_unix.c
index dc518fc..95b21d2 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -1835,11 +1835,13 @@
     void
 ex_xrestore(exarg_T *eap)
 {
-    if (eap->arg != NULL && STRLEN(eap->arg) > 0)
+    size_t  arglen;
+
+    if (eap->arg != NULL && (arglen = STRLEN(eap->arg)) > 0)
     {
 	if (xterm_display_allocated)
 	    vim_free(xterm_display);
-	xterm_display = (char *)vim_strsave(eap->arg);
+	xterm_display = (char *)vim_strnsave(eap->arg, arglen);
 	xterm_display_allocated = TRUE;
     }
     smsg(_("restoring display %s"), xterm_display == NULL
@@ -2625,7 +2627,7 @@
 
     if (err > 0 && err < sys_nerr)
 	return (sys_errlist[err]);
-    sprintf(er, "Error %d", err);
+    snprintf(er, sizeof(er), "Error %d", err);
     return er;
 }
 #endif
@@ -2662,7 +2664,7 @@
     int		len,
     int		force)		// also expand when already absolute path
 {
-    int		l;
+    int		buflen = 0;
 #ifdef HAVE_FCHDIR
     int		fd = -1;
     static int	dont_fchdir = FALSE;	// TRUE when fchdir() doesn't work
@@ -2778,6 +2780,8 @@
 	}
 	if (p != NULL)
 	{
+	    int	l;
+
 #ifdef HAVE_FCHDIR
 	    if (fd >= 0)
 	    {
@@ -2800,23 +2804,29 @@
 	    close(fd);
 #endif
 
-	l = STRLEN(buf);
-	if (l >= len - 1)
+	buflen = (int)STRLEN(buf);
+	if (buflen >= len - 1)
 	    retval = FAIL; // no space for trailing "/"
 #ifndef VMS
-	else if (l > 0 && buf[l - 1] != '/' && *fname != NUL
+	else if (buflen > 0 && buf[buflen - 1] != PATHSEP && *fname != NUL
 						   && STRCMP(fname, ".") != 0)
-	    STRCAT(buf, "/");
+	{
+	    STRCPY(buf + buflen, PATHSEPSTR);
+	    buflen += STRLEN_LITERAL(PATHSEPSTR);
+	}
 #endif
     }
 
+    if (buflen == 0)
+	buflen = (int)STRLEN(buf);
+
     // Catch file names which are too long.
-    if (retval == FAIL || (int)(STRLEN(buf) + STRLEN(fname)) >= len)
+    if (retval == FAIL || (int)(buflen + STRLEN(fname)) >= len)
 	return FAIL;
 
     // Do not append ".", "/dir/." is equal to "/dir".
     if (STRCMP(fname, ".") != 0)
-	STRCAT(buf, fname);
+	STRCPY(buf + buflen, fname);
 
     return OK;
 }
@@ -2850,6 +2860,7 @@
 {
     struct stat st;
     char_u	*slash, *tail;
+    size_t	taillen;
     DIR		*dirp;
     struct dirent *dp;
 
@@ -2874,12 +2885,13 @@
     if (dirp == NULL)
 	return;
 
+    taillen = STRLEN(tail);
     while ((dp = readdir(dirp)) != NULL)
     {
 	// Only accept names that differ in case and are the same byte
 	// length. TODO: accept different length name.
 	if (STRICMP(tail, dp->d_name) == 0
-		&& STRLEN(tail) == STRLEN(dp->d_name))
+		&& taillen == STRLEN(dp->d_name))
 	{
 	    char_u	newname[MAXPATHL + 1];
 	    struct stat st2;
@@ -3054,8 +3066,7 @@
     if (size <= 0)
 	return;
 
-    for (index = 0 ; index < (int)(sizeof(smack_copied_attributes)
-			      / sizeof(smack_copied_attributes)[0]) ; index++)
+    for (index = 0 ; index < (int)ARRAY_LENGTH(smack_copied_attributes); index++)
     {
 	// get the name of the attribute to copy
 	name = smack_copied_attributes[index];
@@ -3220,12 +3231,19 @@
     vim_acl_solaris_T   *aclent;
 
     aclent = malloc(sizeof(vim_acl_solaris_T));
+    if (aclent == NULL)
+	return NULL;
     if ((aclent->acl_cnt = acl((char *)fname, GETACLCNT, 0, NULL)) < 0)
     {
 	free(aclent);
 	return NULL;
     }
     aclent->acl_entry = malloc(aclent->acl_cnt * sizeof(aclent_t));
+    if (aclent->acl_entry == NULL)
+    {
+	free(aclent);
+	return NULL;
+    }
     if (acl((char *)fname, GETACL, aclent->acl_cnt, aclent->acl_entry) < 0)
     {
 	free(aclent->acl_entry);
@@ -3240,13 +3258,15 @@
 
     aclsize = sizeof(struct acl);
     aclent = malloc(aclsize);
+    if (aclent == NULL)
+	return NULL;
     if (statacl((char *)fname, STX_NORMAL, aclent, aclsize) < 0)
     {
 	if (errno == ENOSPC)
 	{
 	    aclsize = aclent->acl_len;
 	    aclent = realloc(aclent, aclsize);
-	    if (statacl((char *)fname, STX_NORMAL, aclent, aclsize) < 0)
+	    if (aclent == NULL || statacl((char *)fname, STX_NORMAL, aclent, aclsize) < 0)
 	    {
 		free(aclent);
 		return NULL;
@@ -3399,7 +3419,11 @@
 mch_can_exe(char_u *name, char_u **path, int use_path)
 {
     char_u	*buf;
+    size_t	bufsize;
+    size_t	buflen;
     char_u	*p, *e;
+    char_u	*p_end;
+    size_t	elen;
     int		retval;
 
     // When "use_path" is false and if it's an absolute or relative path don't
@@ -3425,7 +3449,9 @@
     p = (char_u *)getenv("PATH");
     if (p == NULL || *p == NUL)
 	return -1;
-    buf = alloc(STRLEN(name) + STRLEN(p) + 2);
+    p_end = p + STRLEN(p);
+    bufsize = STRLEN(name) + (size_t)(p_end - p) + 2;
+    buf = alloc(bufsize);
     if (buf == NULL)
 	return -1;
 
@@ -3437,15 +3463,18 @@
     {
 	e = (char_u *)strchr((char *)p, ':');
 	if (e == NULL)
-	    e = p + STRLEN(p);
-	if (e - p <= 1)		// empty entry means current dir
-	    STRCPY(buf, "./");
-	else
+	    e = p_end;
+	elen = (size_t)(e - p);
+	if (elen <= 1)		// empty entry means current dir
 	{
-	    vim_strncpy(buf, p, e - p);
-	    add_pathsep(buf);
+	    p = (char_u *)"./";
+	    elen = STRLEN_LITERAL("./");
 	}
-	STRCAT(buf, name);
+	buflen = vim_snprintf((char *)buf, bufsize, "%.*s%s%s",
+		(int)elen,
+		p,
+		(after_pathsep(p, p + elen)) ? "" : PATHSEPSTR,
+		name);
 	retval = executable_file(buf);
 	if (retval == 1)
 	{
@@ -3454,7 +3483,7 @@
 		if (buf[0] != '/')
 		    *path = FullName_save(buf, TRUE);
 		else
-		    *path = vim_strsave(buf);
+		    *path = vim_strnsave(buf, buflen);
 	    }
 	    break;
 	}
@@ -5233,13 +5262,13 @@
 			linenr_T    lnum = curbuf->b_op_start.lnum;
 			int	    written = 0;
 			char_u	    *lp = ml_get(lnum);
-			size_t	    l;
+			size_t	    lplen = (size_t)ml_get_len(lnum);
 
 			close(fromshell_fd);
 			for (;;)
 			{
-			    l = STRLEN(lp + written);
-			    if (l == 0)
+			    lplen -= written;
+			    if (lplen == 0)
 				len = 0;
 			    else if (lp[written] == NL)
 				// NL -> NUL translation
@@ -5249,10 +5278,10 @@
 				char_u	*s = vim_strchr(lp + written, NL);
 
 				len = write(toshell_fd, (char *)lp + written,
-					   s == NULL ? l
+					   s == NULL ? lplen
 					      : (size_t)(s - (lp + written)));
 			    }
-			    if (len == (int)l)
+			    if (len == (int)lplen)
 			    {
 				// Finished a line, add a NL, unless this line
 				// should not have one.
@@ -5272,6 +5301,7 @@
 				    break;
 				}
 				lp = ml_get(lnum);
+				lplen = ml_get_len(lnum);
 				written = 0;
 			    }
 			    else if (len > 0)
@@ -6089,14 +6119,14 @@
     char_u	numbuf[NUMBUFLEN];
 
     if (sig == SIGKILL)
-	return vim_strsave((char_u *)"kill");
+	return vim_strnsave((char_u *)"kill", STRLEN_LITERAL("kill"));
 
     for (i = 0; signal_info[i].sig != -1; i++)
 	if (sig == signal_info[i].sig)
 	    return strlow_save((char_u *)signal_info[i].name);
 
-    vim_snprintf((char *)numbuf, NUMBUFLEN, "%d", sig);
-    return vim_strsave(numbuf);
+    i = vim_snprintf((char *)numbuf, NUMBUFLEN, "%d", sig);
+    return vim_strnsave(numbuf, i);
 }
 
     char *
@@ -6852,7 +6882,9 @@
      */
     int		j;
     char_u	*tempname;
+    size_t	tempnamelen;
     char_u	*command;
+    size_t	commandlen;
     FILE	*fd;
     char_u	*buffer;
 #define STYLE_ECHO	0	// use "echo", the default
@@ -6866,10 +6898,14 @@
     int		check_spaces;
     static int	did_find_nul = FALSE;
     int		ampersand = FALSE;
-		// vimglob() function to define for Posix shell
-    static char *sh_vimglob_func = "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
-		// vimglob() function with globstar setting enabled, only for bash >= 4.X
-    static char *sh_globstar_opt = "[[ ${BASH_VERSINFO[0]} -ge 4 ]] && shopt -s globstar; ";
+#define STRING_INIT(s) \
+		{(char_u *)(s), STRLEN_LITERAL(s)}
+				// vimglob() function to define for Posix shell
+    static string_T sh_vimglob_func = STRING_INIT("vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >");
+				// vimglob() function with globstar setting enabled, only for bash >= 4.X
+    static string_T sh_globstar_opt = STRING_INIT("[[ ${BASH_VERSINFO[0]} -ge 4 ]] && shopt -s globstar; ");
+#undef STRING_INIT
+
 
     *num_file = 0;	// default: no files found
     *file = NULL;
@@ -6943,12 +6979,12 @@
     // Compute the length of the command.  We need 2 extra bytes: for the
     // optional '&' and for the NUL.
     // Worst case: "unset nonomatch; print -N >" plus two is 29
-    len = STRLEN(tempname) + 29;
+    tempnamelen = STRLEN(tempname);
+    len = tempnamelen + 29;
     if (shell_style == STYLE_VIMGLOB)
-	len += STRLEN(sh_vimglob_func);
+	len += sh_vimglob_func.length;
     else if (shell_style == STYLE_GLOBSTAR)
-	len += STRLEN(sh_vimglob_func)
-	     + STRLEN(sh_globstar_opt);
+	len += sh_vimglob_func.length + sh_globstar_opt.length;
 
     for (i = 0; i < num_pat; ++i)
     {
@@ -6985,63 +7021,62 @@
     if (shell_style == STYLE_BT)
     {
 	// change `command; command& ` to (command; command )
-	STRCPY(command, "(");
-	STRCAT(command, pat[0] + 1);		// exclude first backtick
-	p = command + STRLEN(command) - 1;
-	*p-- = ')';				// remove last backtick
-	while (p > command && VIM_ISWHITE(*p))
-	    --p;
-	if (*p == '&')				// remove trailing '&'
+	commandlen = vim_snprintf((char *)command, len, "(%s)>%s", pat[0] + 1, tempname);   // +1 to exclude first backtick
+
+	p = (char_u *)strstr((char *)command, ")>");
+	if (p == NULL)
 	{
-	    ampersand = TRUE;
-	    *p = ' ';
+	    ;					    // can't happen ;-)
 	}
-	STRCAT(command, ">");
+	else
+	{
+	    --p;
+	    while (p > command && VIM_ISWHITE(*p))
+		--p;
+	    if (*p == '`')			    // remove backtick
+		*p = ' ';
+
+	    --p;
+	    while (p > command && VIM_ISWHITE(*p))
+		--p;
+	    if (*p == '&')			    // remove ampersand
+	    {
+		ampersand = TRUE;
+		*p = ' ';
+	    }
+	}
     }
     else
     {
-	STRCPY(command, "");
 	if (shell_style == STYLE_GLOB)
 	{
 	    // Assume the nonomatch option is valid only for csh like shells,
 	    // otherwise, this may set the positional parameters for the shell,
 	    // e.g. "$*".
-	    if (flags & EW_NOTFOUND)
-		STRCAT(command, "set nonomatch; ");
-	    else
-		STRCAT(command, "unset nonomatch; ");
+	    commandlen = vim_snprintf((char *)command, len, "%sset nonomatch; glob >%s",
+				(flags & EW_NOTFOUND) ? "" : "un", tempname);
 	}
-	if (shell_style == STYLE_GLOB)
-	    STRCAT(command, "glob >");
 	else if (shell_style == STYLE_PRINT)
-	    STRCAT(command, "print -N >");
+	    commandlen = vim_snprintf((char *)command, len, "print -N >%s", tempname);
 	else if (shell_style == STYLE_VIMGLOB)
-	    STRCAT(command, sh_vimglob_func);
+	    commandlen = vim_snprintf((char *)command, len, "%s%s", sh_vimglob_func.string, tempname);
 	else if (shell_style == STYLE_GLOBSTAR)
-	{
-	    STRCAT(command, sh_globstar_opt);
-	    STRCAT(command, sh_vimglob_func);
-	}
+	    commandlen = vim_snprintf((char *)command, len, "%s%s%s", sh_globstar_opt.string,
+				sh_vimglob_func.string, tempname);
 	else
-	    STRCAT(command, "echo >");
-    }
+	    commandlen = vim_snprintf((char *)command, len, "echo >%s", tempname);
 
-    STRCAT(command, tempname);
-
-    if (shell_style != STYLE_BT)
 	for (i = 0; i < num_pat; ++i)
 	{
 	    // When using system() always add extra quotes, because the shell
 	    // is started twice.  Otherwise put a backslash before special
 	    // characters, except inside ``.
 #ifdef USE_SYSTEM
-	    STRCAT(command, " \"");
-	    STRCAT(command, pat[i]);
-	    STRCAT(command, "\"");
+	    commandlen += vim_snprintf((char *)command + commandlen, len, " \"%s\"", pat[i]);
 #else
 	    int intick = FALSE;
 
-	    p = command + STRLEN(command);
+	    p = command + commandlen;
 	    *p++ = ' ';
 	    for (j = 0; pat[i][j] != NUL; ++j)
 	    {
@@ -7070,12 +7105,14 @@
 		*p++ = pat[i][j];
 	    }
 	    *p = NUL;
+	    commandlen = (size_t)(p - command);
 #endif
 	}
+    }
     if (flags & EW_SILENT)
 	show_shell_mess = FALSE;
     if (ampersand)
-	STRCAT(command, "&");		// put the '&' after the redirection
+	STRCPY(command + commandlen, "&");		// put the '&' after the redirection
 
     /*
      * Using zsh -G: If a pattern has no matches, it is just deleted from
@@ -8133,7 +8170,8 @@
     char_u		buf[50];
     char_u		*strp;
     long		got_hints;
-    static char_u	*mouse_code;
+    static char_u	*mouse_code = NULL;
+    static size_t	mouse_codelen = 0;
     static char_u	mouse_name[2] = {KS_MOUSE, KE_FILLER};
     static int		prev_row = 0, prev_col = 0;
     static XSizeHints	xterm_hints;
@@ -8156,6 +8194,8 @@
 
 	// Rely on the same mouse code for the duration of this
 	mouse_code = find_termcode(mouse_name);
+	if (mouse_code != NULL)
+	    mouse_codelen = STRLEN(mouse_code);
 	prev_row = mouse_row;
 	prev_col = mouse_col;
 	xterm_trace = 2;
@@ -8174,7 +8214,8 @@
 	    xterm_hints.x = 2;
 	return TRUE;
     }
-    if (mouse_code == NULL || STRLEN(mouse_code) > 45)
+
+    if (mouse_code == NULL || mouse_codelen > 45)
     {
 	xterm_trace = 0;
 	return FALSE;
@@ -8189,12 +8230,12 @@
 	return TRUE;
 
     STRCPY(buf, mouse_code);
-    strp = buf + STRLEN(buf);
+    strp = buf + mouse_codelen;
     *strp++ = (xterm_button | MOUSE_DRAG) & ~0x20;
     *strp++ = (char_u)(col + ' ' + 1);
     *strp++ = (char_u)(row + ' ' + 1);
-    *strp = 0;
-    add_to_input_buf(buf, STRLEN(buf));
+    *strp = NUL;
+    add_to_input_buf(buf, strp - buf);
 
     prev_row = row;
     prev_col = col;