This file is umask.def, from which is created umask.c.
It implements the builtin "umask" in Bash.

Copyright (C) 1987-2022 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Bash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES umask.c

$BUILTIN umask
$FUNCTION umask_builtin
$SHORT_DOC umask [-p] [-S] [mode]
Display or set file mode mask.

Sets the user file-creation mask to MODE.  If MODE is omitted, prints
the current value of the mask.

If MODE begins with a digit, it is interpreted as an octal number;
otherwise it is a symbolic mode string like that accepted by chmod(1).

Options:
  -p	if MODE is omitted, output in a form that may be reused as input
  -S	makes the output symbolic; otherwise an octal number is output

Exit Status:
Returns success unless MODE is invalid or an invalid option is given.
$END

#include <config.h>

#include "../bashtypes.h"
#include "filecntl.h"
#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
#  include <sys/file.h>
#endif

#if defined (HAVE_UNISTD_H)
#include <unistd.h>
#endif

#include <stdio.h>
#include <chartypes.h>

#include "../bashintl.h"

#include "../shell.h"
#include "posixstat.h"
#include "common.h"
#include "bashgetopt.h"

/* **************************************************************** */
/*                                                                  */
/*                     UMASK Builtin and Helpers                    */
/*                                                                  */
/* **************************************************************** */

static void print_symbolic_umask (mode_t);
static int symbolic_umask (WORD_LIST *);

/* Set or display the mask used by the system when creating files.  Flag
   of -S means display the umask in a symbolic mode. */
int
umask_builtin (WORD_LIST *list)
{
  int print_symbolically, opt, umask_value, pflag;
  mode_t umask_arg;

  print_symbolically = pflag = 0;
  reset_internal_getopt ();
  while ((opt = internal_getopt (list, "Sp")) != -1)
    {
      switch (opt)
	{
	case 'S':
	  print_symbolically++;
	  break;
	case 'p':
	  pflag++;
	  break;
	CASE_HELPOPT;
	default:
	  builtin_usage ();
	  return (EX_USAGE);
	}
    }

  list = loptend;

  if (list)
    {
      if (DIGIT (*list->word->word))
	{
	  umask_value = read_octal (list->word->word);

	  /* Note that other shells just let you set the umask to zero
	     by specifying a number out of range.  This is a problem
	     with those shells.  We don't change the umask if the input
	     is lousy. */
	  if (umask_value == -1)
	    {
	      sh_erange (list->word->word, _("octal number"));
	      return (EXECUTION_FAILURE);
	    }
	}
      else
	{
	  umask_value = symbolic_umask (list);
	  if (umask_value == -1)
	    return (EXECUTION_FAILURE);
	}
      umask_arg = (mode_t)umask_value;
      umask (umask_arg);
      if (print_symbolically)
	print_symbolic_umask (umask_arg);
    }
  else				/* Display the UMASK for this user. */
    {
      umask_arg = umask (022);
      umask (umask_arg);

      if (pflag)
	printf ("umask%s ", (print_symbolically ? " -S" : ""));
      if (print_symbolically)
	print_symbolic_umask (umask_arg);
      else
	printf ("%04lo\n", (unsigned long)umask_arg);
    }

  return (sh_chkwrite (EXECUTION_SUCCESS));
}

/* Print the umask in a symbolic form.  In the output, a letter is
   printed if the corresponding bit is clear in the umask. */
static void
print_symbolic_umask (mode_t um)
{
  char ubits[4], gbits[4], obits[4];		/* u=rwx,g=rwx,o=rwx */
  int i;

  i = 0;
  if ((um & S_IRUSR) == 0)
    ubits[i++] = 'r';
  if ((um & S_IWUSR) == 0)
    ubits[i++] = 'w';
  if ((um & S_IXUSR) == 0)
    ubits[i++] = 'x';
  ubits[i] = '\0';

  i = 0;
  if ((um & S_IRGRP) == 0)
    gbits[i++] = 'r';
  if ((um & S_IWGRP) == 0)
    gbits[i++] = 'w';
  if ((um & S_IXGRP) == 0)
    gbits[i++] = 'x';
  gbits[i] = '\0';

  i = 0;
  if ((um & S_IROTH) == 0)
    obits[i++] = 'r';
  if ((um & S_IWOTH) == 0)
    obits[i++] = 'w';
  if ((um & S_IXOTH) == 0)
    obits[i++] = 'x';
  obits[i] = '\0';

  printf ("u=%s,g=%s,o=%s\n", ubits, gbits, obits);
}

static inline mode_t
copyuser (mode_t mask)
{
  return ((mask & S_IRUSR) ? S_IRUGO : 0) |
	 ((mask & S_IWUSR) ? S_IWUGO : 0) |
	 ((mask & S_IXUSR) ? S_IXUGO : 0);
}

static inline mode_t
copygroup (mode_t mask)
{
  return ((mask & S_IRGRP) ? S_IRUGO : 0) |
	 ((mask & S_IWGRP) ? S_IWUGO : 0) |
	 ((mask & S_IXGRP) ? S_IXUGO : 0);
}

static inline mode_t
copyother (mode_t mask)
{
  return ((mask & S_IROTH) ? S_IRUGO : 0) |
	 ((mask & S_IWOTH) ? S_IWUGO : 0) |
	 ((mask & S_IXOTH) ? S_IXUGO : 0);
}

int
parse_symbolic_mode (char *mode, mode_t initial_bits)
{
  char op, c;
  mode_t who, perm, bits;
  char *s;

  for (s = mode, bits = initial_bits;;)
    {
      who = 0;
      op = 0;

      /* Parse the `who' portion of the symbolic mode clause. */
      while (member (*s, "agou"))
	{
	  switch (c = *s++)
	    {
	    case 'u':
	      who |= S_IRWXU;
	      continue;
	    case 'g':
	      who |= S_IRWXG;
	      continue;
	    case 'o':
	      who |= S_IRWXO;
	      continue;
	    case 'a':
	      who |= S_IRWXU | S_IRWXG | S_IRWXO;
	      continue;
	    default:
	      break;
	    }
	}

      /* default `who' is `a' */
      if (who == 0)
	who = S_IRWXU | S_IRWXG | S_IRWXO;

      /* The operation is now sitting in *s. */
start_op:
      perm = 0;
      op = *s++;
      switch (op)
	{
	case '+':
	case '-':
	case '=':
	  break;
	default:
	  builtin_error (_("`%c': invalid symbolic mode operator"), op);
	  return (-1);
	}

      /* Parse out the `action' section of the symbolic mode clause. An
	 action can be a set of permissions (rwxXst), a copy specification
	 (ugo), or another op (+-=). */
      while (member (*s, "rwxXstugo"))
	{
	  switch (*s)
	    {
	    /* First the copy specification */
	    case 'u':
	      perm = copyuser (initial_bits);
	      break;
	    case 'g':
	      perm = copygroup (initial_bits);
	      break;
	    case 'o':
	      perm = copyother (initial_bits);
	      break;

	    /* Then the permissions. */
	    case 'r':
	      perm |= S_IRUGO;
	      break;
	    case 'w':
	      perm |= S_IWUGO;
	      break;
	    case 'X':
	      /* for chmod, this includes S_ISDIR but that doesn't make sense for umask */
#if defined (S_IFDIR)
	      if (this_shell_builtin != umask_builtin && (initial_bits & S_IFDIR) == 0)
		break;
#endif
	      if ((initial_bits & S_IXUGO) == 0)
		break;		/* no-op if original mask doesn't include execute bits */
	      /* FALLTHROUGH */
	    case 'x':
	      perm |= S_IXUGO;
	      break;
	    case 's':
#ifdef S_ISUID
	      perm |= S_ISUID | S_ISGID;
	      break;
#else
	      goto spec_error;
#endif
	    case 't':
#ifdef S_ISVTX
	      perm |= S_ISVTX;
	      break;
#else
	      goto spec_error;
#endif
	    }
	  s++;
	}

      /* Now perform the operation or return an error for a
	 bad permission string. */
      perm &= who;

      switch (op)
	{
	case '+':
	  bits |= perm;
	  break;
	case '-':
	  bits &= ~perm;
	  break;
	case '=':
	  bits &= ~who;
	  bits |= perm;
	  break;
	}

      /* Break if the end of the action string, loop if we're going to parse
	 another `who', go back to parsing another op if we have an op spec
	 (+-=). Return an invalid mode character error for everything else. */
      if (*s == '\0')
	break;
      else if (*s == ',')
	s++;	/* skip past ',' */
      else if (*s == '+' || *s == '-' || *s == '=')
	goto start_op;
      else
	{
spec_error:
	  builtin_error (_("`%c': invalid symbolic mode character"), *s);
	  return (-1);
	}
    }

  return (bits);
}

/* Set the umask from a symbolic mode string similar to that accepted
   by chmod.  If the -S argument is given, then print the umask in a
   symbolic form. */
static int
symbolic_umask (WORD_LIST *list)
{
  mode_t um;
  int bits;

  /* Get the initial umask.  Don't change it yet. */
  um = umask (022);
  umask (um);

  /* All work is done with the complement of the umask -- it's
     more intuitive and easier to deal with.  It is complemented
     again before being returned. */
  bits = parse_symbolic_mode (list->word->word, ~um & 0777);
  if (bits == -1)
    return (-1);

  um = ~bits & 0777;
  return (um);
}
