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:
- Wait for 4 hours.
- 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.
- For each B0/B1 pair, change the owner of the link name located in the journal entry to the user profile RESERVE.
- 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:
- Wait for 4 hours.
- Call
Qp0lGetAttr()
requesting theQP0L_ATTR_MODIFY_TIME
attribute for the /Restaurant/Reservations2019/July directory. - 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. - 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:
- Wait for 4 hours.
- Change the ownership of all the objects under /Restaurant/Reservations2019 to the user profile, RESERVE.
- 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’)
Figure 2: DSPAUT OBJ(‘/Sales2019/July’)
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.
Connect with the the IBM i community
Connect, learn, share, and engage with other IBM i users as you follow what’s trending and join the discussion. Join now
References
Refer to the following locations in the IBM Knowledge Center for additional information:
APIs
- stat()-Get File Information
- chown()-Change Owner and Group of File
- QlgChown()-Change Owner and Group of File (using NLS-enabled path name)
- QjoRetrieveJournalEntries()-Retrieve Journal Entries
- Qp0lGetAttr()-Get Attributes
- Qp0lSetAttr()-Set Attributes
Commands
- Change Attribute (CHGATR)
- Change Owner (CHGOWN)
- Change Primary Group (CHGPGP)
- Delete User Profile (DLTUSRPRF)
- Display Attributes (DSPATR)
- Display Authority (DSPAUT)
- Start Journal (STRJRN)
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);
}