Who owns my IBM i integrated file system object?

The ownership of objects in the integrated file system has long been a confusing topic for users specifically for the root, QOpenSys and user-defined file systems. It is important to understand that the integrated file system was designed to follow Portable Operating System Interface (POSIX) standards which clearly defines how the owner and primary group are to be set for new objects. The owner is the user creating the object. The primary group will be either the group of the owner or the primary group of the new object’s parent directory and is set based on the value of the SISGID mode bit of the parent directory. This is very different from the _traditional or native IBM i concept where the user profile can indicate that the user’s group profile is to own all objects created by the user with the OWNER(*GRPPRF) field of the user profile.

This difference in behavior from native operations is often noticed in one of following two ways.

  • Object existence: Assigning ownership of objects to the group of the creating user may prevent inadvertent deletion of important objects if you use the OWNOBJOPT(*DLT) option in the Delete User Profile (DLTUSRPRF) command. Typically, group user profiles are not likely to be removed from the system very often. By having the group own the objects, there is a much lower risk that important objects will be deleted.

  • Object access: Assigning the ownership of objects to the group of the creating user will provide the correct authority for everyone who needs to access the objects. When the ownership of the objects is not assigned to the group, users who expect to be able to perform operations on newly created objects will not have access. This causes the needed functions to fail.

So, how do you manage this?

Consider the goal. Do you want to control ownership so that objects aren’t inadvertently deleted? Do you want users to get authority through their group profile?

Object existence

Suppose you really want all objects owned by a specific user profile, this cannot be done automatically with objects in the integrated file system. There are a few options that can be used to monitor for the creation of new objects and then change the ownership for the new objects.

The first option is to monitor for the journal entries that indicate an object was created and linked in the journaled directory. Journal entries with code B – Integrated file system, types B0 – Begin create and B1 – Create summary are generated for new objects linked into the journaled directory. These entries contain the object type (such as *DIR, *STMF, and so on) and the link name (in Unicode) for the new object. A job can be run as often as needed to retrieve the journal entries of this type and assign ownership for each new object.

For example, a restaurant sets up a directory structure to track reservations: /Restaurant/Reservations2019. The restaurant wants all objects under this directory to be owned by user profile RESERVE. User journaling has been started specifying the journal RESERVE and indicating new objects will also be journaled as child directories can be created.

STRJRN OBJ(('/Restaurant/Reservations2019')) JRN('/QSYS.LIB/JRN.LIB/RESERVE.JRN') INHERIT(*YES)

The restaurant does not need the ownership set on the new objects immediately but would like the owner to be updated within 4 hours of the objects being created. One solution in this case is to write a program that runs in a batch job that would perform the following steps:

  1. Wait for 4 hours.
  2. Call the Retrieve Journal Entries (QjoRetrieveJournalEntries) API requesting entries that were created in the last 4 hours that have the entry type of B0 or B1.
  3. For each B0/B1 pair, change the owner of the link name located in the journal entry to the user profile RESERVE.
  4. Go back to step 1 and start again.

The owner for an object can be set by using the chown()-Change Owner and Group of a File, or QlgChgown()-Change Owner and Group of File (using NLS-enabled path name) APIs and specifying the user ID and group ID, or by using the Change Owner (CHGOWN) CL command. For example:

CHGOWN OBJ('/Restaurant/Reservations2019/July/GeorgeSmith15') NEWOWN(RESERVE)

See the sample program for an example of how this can be done. The sample program may not take into account everything that may occur in your environment. For example, changing the system time or if the buffer provided doesn’t hold all the entries that can be retrieved might require additional considerations to ensure all the needed journal entries have been retrieved

The second option is to monitor for any change to the contents of the directory. The Data change date/time field for a directory is updated when a change is made to an entry in the directory or if a new entry is added. This timestamp is returned in the st_mtime field by the stat()-Get File Information API or retrieved directly with the Qp0lGetAttr()-Get Attributes API requesting the QP0L_ATTR_MODIFY_TIME attribute. If this timestamp has changed since the last time it was checked, the ownership for all objects in the directory can then be set to the required user profile. Using the example above, a similar program can be written to perform the following steps:

  1. Wait for 4 hours.
  2. Call Qp0lGetAttr() requesting the QP0L_ATTR_MODIFY_TIME attribute for the /Restaurant/Reservations2019/July directory.
  3. If the difference between the current time and the time returned from Qp0lGetAttr() is not more than 4 hours, change the ownership for all the objects in the directory to RESERVE.
  4. Go back to step 1 and start again.

In this example, the SUBTREE(*ALL) option is used in the CHGOWN command:

CHGOWN OBJ('/Restaurant/Reservations2019/July') NEWOWN(RESERVE) SUBTREE(*ALL)

Another option is to have a job run to change the owner of all objects in the entire subtree. Using the example above, the restaurant managers decide it is less efficient to check for a time change or read through journal entries. So they decide to create a program to run every 4 hours to run the CHGOWN command against the entire subtree. The program would have the following steps:

  1. Wait for 4 hours.
  2. Change the ownership of all the objects under /Restaurant/Reservations2019 to the user profile, RESERVE.
  3. Go to step 1

In this case, because the restaurant managers know there will be a new directory each month, the command is run against the /Restaurant/Reservations2019 directory.

CHGOWN OBJ('/Restaurant/Reservations2019') NEWOWN(RESERVE) SUBTREE(*ALL)

Any of these options will achieve the required results to have all the objects linked under the /Restaurant/Reservations2019 directory to all be owned by the RESERVE user profile. Note that the use of an API or command to change the owner will update the attribute displayed by the Display Attributes (DSPATR) command as Attribute change date/time of the object even if the owner is not changed. For this reason, it may be desirable to only change the specific objects instead of using SUBTREE(*ALL) if your backup processing or applications are sensitive to this.

Object access

In this case, a group of users need access to the objects in the directory. This can be accomplished by having the users get authority through the primary group of the new objects. As stated earlier, the primary group of a new object is assigned based on the value of the S_ISGID mode bit of the parent directory. This bit is defined as the Set group ID on execution bit and is displayed as the Set effective group ID attribute when the DSPATR command is used. When the value of this attribute on the parent directory is set to Yes, the primary group for the new objects created in the directory is set to the primary group of the directory. When the value of this attribute is set to No, the primary group of the new objects is set to the primary group of the user creating the object. To provide access to everyone in a group to all new objects in a specific directory, set the primary group of the directory to the group profile and set the Set effective group ID attribute to Yes.

For example, I have a set of users (George, Ben, and Sally) who need access to all the new files in the directory, /Sales2019/July. George, Ben, and Sally are all part of the group, SALESREP. As an administrator, I have set up /Sales2019/July so that the Set effective group ID attribute is set to Yes, and the primary group for this directory is set to SALESREP with the needed permissions of *RWX. An example of what this information might look like when displayed using the DSPATR and Display Authority (DSPAUT) commands can be seen in Figures 1 and 2. Now when George creates a file, /Sales2019/July/AcmeLumber10July, George will own it, but Ben and Sally will have access to the file as needed

Figure 1: DSPATR OBJ(‘/Sales2019/July’)

figure1

Figure 2: DSPAUT OBJ(‘/Sales2019/July’)

figure2

The primary group for an object can be set by using the chown() or QlgChgown() APIs and specifying the user ID and group ID, or by using the Change Primary Group (CHGPGP) CL command:

CHGPGP OBJ('/Sales2019/July') NEWPGP(SALESREP) DTAAUT(*RWX) OBJAUT(*ALL)

The Set effective group ID attribute can be set by using the Qp0lSetAttr()–Set Attributes API to set the QP0L_ATTR_SGID attribute, or by using the Change Attribute (CHGATR) CL command:

CHGATR OBJ('/Sales2019/July') ATR(*SETGID) VALUE(*YES)

Setting the primary group and Set effective group ID attribute on the parent directory enables users to access newly created objects as expected. If this still does not provide the needed access, one of the earlier examples may be used to assign ownership to the group. It is important to understand however, when setting the owner of an object on IBM i the owner and the primary group may not be the same profile.

Summary

This article described different options to ensure that the ownership or authorities of new integrated file systems is set as needed in your environment.

As you define the policies for ownership and object security on your system, consider these options for the integrated file system.

References

Refer to the following locations in the IBM Knowledge Center for additional information:

APIs
Commands

Appendix – Sample Program

Sample.c
//---------------------------------------------------------------------
// Sample program to retrieve B0 and B1 journal entries
//---------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <qusec.h>    /* Error code structure */
#include <qp0ljrnl.h> /* entry structs defined*/
#include <qjournal.h> /* QjoRetrieveJournalEntries()     */
#include <qtqiconv.h> // iconv
#include <errno.h>    /* use errno values     */

//
// struct TimeRange
//
//  Starting and ending time of the variation--used when retrieving
//  journal entries.
//
typedef struct TimeRange {
    time_t startTime;
    time_t endTime;
} TimeRange_t;

// struct JournalEntryTypeKeyArray
//
// Structure to contain the B0 and B1 entry types to be retrieved
// from the journal
//
typedef _Packed struct JournalEntryTypeKeyArray
{
  Qjo_JE_Data_Key_8_t   EntryTypeKeyFixed;
  Qjo_Jrn_Entry_Type_t  Jrn_Entry_Type[2];
} JournalEntryTypeKeyArray_t;

//---------------------------------------------------------------------
// SetTimeRangeStartValue()
//---------------------------------------------------------------------
int  SetTimeRangeStartValue(TimeRange_t *rtr)
{
  /* get current time, set value in structure */
  time(&(rtr->startTime));
  return(0);
}

//---------------------------------------------------------------------
// SetTimeRangeEndValue()
//---------------------------------------------------------------------
int  SetTimeRangeEndValue(TimeRange_t *rtr)
{
  /* get current time, set value in structure */
  time(&(rtr->endTime));
  return(0);
}

//---------------------------------------------------------------------
// convert_time_to_SAA_string()
//---------------------------------------------------------------------
int convert_time_to_SAA_string(time_t ntime, char *outtime)
{
  struct tm tm_time;
  size_t rv;
  char timestring[27];

  // convert to tm struct
  localtime_r(&ntime, &tm_time);
  rv = strftime(timestring, 27, "%Y-%m-%d-%H.%M.%S.000000", &tm_time);
  if (rv == 0)
  {
    return(-1);
  }
  memcpy (outtime, timestring, 26);
  // return ok
  return(0);
}

//---------------------------------------------------------------------
// setupSelectionCriteria()
//---------------------------------------------------------------------
int setupSelectionCriteria(char *selectCriteria,
                           int selectCriteriaLength,
                           TimeRange_t *rtr)
{
  Qjo_JE_Jrn_Info_Retrieve_t *selCriteria_p;
  Qjo_JE_Fmt_Var_Len_Rcrd_t  *varRec_p;
  Qjo_JE_Data_Key_3_t        *dataKey3_p;
  Qjo_JE_Data_Key_5_t        *dataKey5_p;
  JournalEntryTypeKeyArray_t *dataKey8_p;
  int      offset = 0;
  int      rv;

  // initialize structure
  memset (selectCriteria, 0x00, selectCriteriaLength);

  selCriteria_p = (Qjo_JE_Jrn_Info_Retrieve_t *)selectCriteria;
  selCriteria_p->Num_Var_Len_Rcrds = 3;
  offset += sizeof(Qjo_JE_Jrn_Info_Retrieve_t);


  // Fill in first key -- start time
  varRec_p = (Qjo_JE_Fmt_Var_Len_Rcrd_t *)(selectCriteria+offset);
  varRec_p->Len_Var_Len_Rcrd = sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t) +
    sizeof(Qjo_JE_Data_Key_3_t);
  varRec_p->Key = 3;
  varRec_p->Len_Of_Data = sizeof(Qjo_JE_Data_Key_3_t);
  offset += sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t);
  dataKey3_p = (Qjo_JE_Data_Key_3_t *)( &selectCriteria[offset]);
  rv = convert_time_to_SAA_string(rtr->startTime,
                                  dataKey3_p->Starting_Time_Stamp);
  if (rv == -1) // failed to convert time string
  {
    return(-1);
  }
  offset += sizeof(Qjo_JE_Data_Key_3_t);

  // Fill in second key -- end time
  varRec_p = (Qjo_JE_Fmt_Var_Len_Rcrd_t *) &selectCriteria[offset];
  varRec_p->Len_Var_Len_Rcrd = sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t) +
    sizeof(Qjo_JE_Data_Key_5_t);
  varRec_p->Key = 5;
  varRec_p->Len_Of_Data = sizeof(Qjo_JE_Data_Key_5_t);
  offset += sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t);
  dataKey5_p = (Qjo_JE_Data_Key_5_t *) &selectCriteria[offset];
  rv = convert_time_to_SAA_string(rtr->endTime,
                                  dataKey5_p->Ending_Time_Stamp);
  if (rv == -1) // failed to convert time string
  {
    return(-1);
  }
  offset += sizeof(Qjo_JE_Data_Key_5_t);

  // Fill in third key -- entry types B0 and B1
  varRec_p = (Qjo_JE_Fmt_Var_Len_Rcrd_t *) &selectCriteria[offset];
  varRec_p->Len_Var_Len_Rcrd = sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t) +
    sizeof(JournalEntryTypeKeyArray_t);
  varRec_p->Key = 8;
  varRec_p->Len_Of_Data = sizeof(JournalEntryTypeKeyArray_t);
  offset += sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t);
  dataKey8_p = (JournalEntryTypeKeyArray_t *) &selectCriteria[offset];
  dataKey8_p->EntryTypeKeyFixed.Num_In_Array = 2;
  memcpy(dataKey8_p->Jrn_Entry_Type[0],"B0        ",
         sizeof(Qjo_Jrn_Entry_Type_t));
  memcpy(dataKey8_p->Jrn_Entry_Type[1],"B1        ",
         sizeof(Qjo_Jrn_Entry_Type_t));

  return(0);
}

//---------------------------------------------------------------------
// convertStringToJobCCSID()
//---------------------------------------------------------------------

int convertStringToJobCCSID(int    in_ccsid,
                          char   *in_buf,
                          char   *out_buf,
                          size_t *in_bytes_left,
                          size_t *out_bytes_left)
{
  QtqCode_T from_code =
  { 0,                           // input CCSID.
    0,                           // IBM default conversion.
    0,                           // do not return substitution count
    0,                           // conv descriptor is not
                                 // returned to initial shift state
    0,                           // inbytesleft required
    0                            // No mixed byte errors
  };

  QtqCode_T to_code =
  { 0,                           // output CCSID
    0,                           // ignored on output
    0,                           // ignored on output
    0,                           // ignored on output
    0,                           // ignored on output
    0                            // ignored on output
  };

  iconv_t cd;                    // conversion descriptor.
  int rc = 0;                    // return value

  from_code.CCSID = in_ccsid;    // set from CCSID
  to_code.CCSID = 0;             // set to job CCSID

  /// Open conversion descriptor
  cd = QtqIconvOpen(&to_code, &from_code);

  if ( cd.return_value == -1 ) // failed, return error
    return(-1);

  // Do the conversion.
  rc = iconv(cd,
             &in_buf,
             in_bytes_left,
             &out_buf,
             out_bytes_left);

  // Close conversion descriptor
  iconv_close(cd);
  return(rc);
}

//---------------------------------------------------------------------
// processB1JournalEntry()
//---------------------------------------------------------------------
int processB1JournalEntry(Qp0l_B1_Journal_Entry_t *B1_entry_p)
{
  char *tmpPtr;
  Qp0l_Path_Name_t *pathName;
  char pathNameString[5000];  // local path in job CCSID
  unsigned int outputPathBufLen = 5000;
  unsigned int inputPathLen;
  Qp0l_Object_Name_t *objName;
  char objNameString[512];
  unsigned int outputObjNameBufLen = 512;
  unsigned int inputObjNameLen;
  int rc;
  char chgownCmdString[10000];

  // Get pathname and object name
  tmpPtr = (char *)B1_entry_p;
  pathName = (Qp0l_Path_Name_t *)(tmpPtr + B1_entry_p->PathOffset);
  inputPathLen = pathName->PathHeader.Path_Length;
  objName = (Qp0l_Object_Name_t *)(tmpPtr + B1_entry_p->NameOffset);
  inputObjNameLen = objName->Length;

  // convert path to job ccsid
  memset(pathNameString,0x00,outputPathBufLen);
  rc = convertStringToJobCCSID(pathName->PathHeader.CCSID,
                             &(pathName->Path[0]),
                             &(pathNameString[0]),
                             &inputPathLen,
                             &outputPathBufLen);
  if (0!=rc) // Conversion of path to job CCSID didn't complete
  {
    return(-1); // failed
  }
  // convert object name to job ccsid
  memset(objNameString,0x00,outputObjNameBufLen);
  rc = convertStringToJobCCSID(objName->NLSinfo.ccsid,
                             &(objName->Name[0]),
                             &(objNameString[0]),
                             &inputObjNameLen,
                             &outputObjNameBufLen);
  if (0!=rc) // Conversion of path to job CCSID didn't complete
  {
    return(-1); // failed
  }

  // Set up CHGOWN command
  memset(chgownCmdString,0x00,10000);

  sprintf(chgownCmdString,
          "CHGOWN OBJ('%s/%s') NEWOWN(RESERVE)",
          pathNameString,objNameString);
  system(chgownCmdString);  // change owner
  return(0);
}

//---------------------------------------------------------------------
//  Retrieve and process B0 and B1 journal entries
//---------------------------------------------------------------------
int  main(  int argc,
            char *argv[])
{
  // local variables
  int         rc,rv;
  int waitTime = (60 * 60 * 4);  // wait 4 hours

  // Variables for QjoRetrieveJournalEntries
  char     formatName[8];
  Qus_EC_t errcode;
  char     msg[7];
  char     journalname[20];  // journal name and library

  // Variables for journal entry criteria
  const int entryCriteriaSize = sizeof(Qjo_JE_Jrn_Info_Retrieve_t) +
                                3 * sizeof(Qjo_JE_Fmt_Var_Len_Rcrd_t) +
                                sizeof(Qjo_JE_Data_Key_3_t) +
                                sizeof(Qjo_JE_Data_Key_5_t) +
                                sizeof(JournalEntryTypeKeyArray_t);
  char     selectCriteria[entryCriteriaSize]; // space for criteria information
  TimeRange_t rtr;  // time range for retrieve

  // Variables for QjoRetrieveJournalEntries receiver variable
  char    *recvar;      // receiver variable pointer
  long int recvarlen;   // length of receiver variable
  int      entryI;
  Qjo_RJNE0100_Hdr_t *header_p;
  Qjo_RJNE0100_JE_Hdr_t *entry_header_p;
  Qjo_RJNE0100_JE_ESD_t *entry_specific_p;
  Qp0l_B1_Journal_Entry_t *B1_entry_p;
  int receiverOffset=0;

  // Set up journal name and library for call to
  // QjoRetrieveJournalEntries
  sprintf (journalname,"%-10.10s%-10.10s", "RESERVE","JRN");
  // Set up formatName variable
  memcpy(formatName, "RJNE0100", 8);

  // retrieve initial start time
  SetTimeRangeStartValue(&rtr);

  // Allocate and setup receiver buffer
  recvarlen = 4 * 1024 * 1024;
  recvar = (char *) malloc(recvarlen);
  if (NULL == recvar)
  {
    // error allocating buffer, exit with error
    return(ENOMEM);
  }

  // Start loop here
  Loop:

  // wait 4 hours
  sleep(waitTime);

  // Retrieve end time
  SetTimeRangeEndValue(&rtr);
  // Set up selection criteria variable with three records,
  // start time, end time and entry type.
  setupSelectionCriteria(selectCriteria, entryCriteriaSize, &rtr);
  // Clear receiver buffer
  memset (recvar, 0x00, recvarlen);
  // Set up error code parameter for QjoRetrieveJournalEntries
  memset((char *)&errcode, 0x00, sizeof(errcode));
  errcode.Bytes_Provided = sizeof(errcode);

  // Call QjoRetrieveJournalEntries()
  QjoRetrieveJournalEntries(recvar,               // Receiver variable
                            &recvarlen,           // Length of receiver
                                                  // variable
                            journalname,          // Qualified journal
                                                  // name
                            formatName,           // format of returned
                                                  // data
                            selectCriteria,       // Selection criteria
                            (void *) &errcode);   // Error code

  // Check for errors, message CPF7062 is OK
  if((errcode.Bytes_Available > 0) &&
     (0 != memcmp(errcode.Exception_Id, "CPF7062",7)))
  {
    free(recvar);
    return(-1);
  }

  // Process returned entries
  header_p = (Qjo_RJNE0100_Hdr_t *) recvar;  // header information
  receiverOffset = header_p->Offset_First_Jrn_Entry; // first entry

  // If no entries returned jump to the top of the loop and wait
  if (header_p->Number_Entries_Retreived == 0)
  {
    goto Loop;
  }

  // Loop through retrieved entries
  for (entryI = 0;
       entryI < header_p->Number_Entries_Retreived;
       entryI++)
  {
    entry_header_p = (Qjo_RJNE0100_JE_Hdr_t *) &recvar[receiverOffset];
    entry_specific_p = (Qjo_RJNE0100_JE_ESD_t *) &recvar[receiverOffset
                       + entry_header_p->Dsp_Entry_Specific_Data];
    B1_entry_p = (Qp0l_B1_Journal_Entry_t *)&(entry_specific_p->ESD);

    // Process B1 entries
    if (0 == memcmp("B1", entry_header_p->Entry_Type, 2))
    {
      // Process the entry
      processB1JournalEntry(B1_entry_p);
    }
    // advance offset to next entry header
    receiverOffset += entry_header_p->Dsp_Next_Jrn_Hdr;
  }
  // use the current end time for the start time in the next loop
  // iteration
  rtr.startTime = rtr.endTime;

  // go to the start of the loop
  goto Loop;

  // free allocated storage
  free(recvar);
  // return ok
  return(0);
}
Margaret Fenlon