Skip to content
4 changes: 4 additions & 0 deletions native/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to the native code for "zowe-native-proto" are documented in

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## Recent Changes

- `c`: Fixed silent error when uss chown user did not have permissions [#565](https://github.com/zowe/zowe-native-proto/pull/565)

## `0.1.9`

- `golang`: Fixed an issue where an empty response on the `HandleReadFileRequest` function would result in a panic. [#550](https://github.com/zowe/zowe-native-proto/pull/550)
Expand Down
141 changes: 122 additions & 19 deletions native/c/zusf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@
*
*/

// z/OS UNIX extensions needed for st_tag in struct stat, etc.
#ifndef _AE_BIMODAL
#define _AE_BIMODAL 1
#endif
#ifndef _OPEN_SYS_FILE_EXT
#define _OPEN_SYS_FILE_EXT 1
#endif
#ifndef _LARGE_TIME_API
#define _LARGE_TIME_API
#endif

#include <limits.h>
#include <limits>
#include <climits>
#ifndef _LARGE_TIME_API
#define _LARGE_TIME_API
#endif
Expand Down Expand Up @@ -49,6 +63,7 @@
#include <time.h>
#include <iomanip>
#include <sstream>
#include <errno.h>

using namespace std;

Expand Down Expand Up @@ -1599,64 +1614,152 @@ short zusf_get_id_from_user_or_group(const string &user_or_group, bool is_user)
return -1;
}

int zusf_chown_uss_file_or_dir(ZUSF *zusf, string file, const string &owner, bool recursive)
/**
* Helper to convert user string to UID.
* Accepts empty (→ -1), numeric UID, or name (getpwnam).
* Returns true if resolved or empty, false if invalid/overflow.
*/
static bool resolve_uid_from_str(const std::string& s, uid_t& out) {
if (s.empty()) { out = (uid_t)-1; return true; } // not user provided
bool digits = s.find_first_not_of("0123456789") == std::string::npos;
if (digits) {
unsigned long v = strtoul(s.c_str(), nullptr, 10);
if (v > std::numeric_limits<uid_t>::max()) return false;
out = static_cast<uid_t>(v);
return true;
}
if (passwd* pw = getpwnam(s.c_str())) { out = pw->pw_uid; return true; }
return false;
}

/**
* Helper to convert group string to GID.
* Accepts empty (→ -1), numeric GID, or name (getgrnam).
* Returns true if resolved or empty, false if invalid/overflow.
*/
static bool resolve_gid_from_str(const std::string& s, gid_t& out) {
if (s.empty()) { out = (gid_t)-1; return true; } // no group provided
bool digits = s.find_first_not_of("0123456789") == std::string::npos;
if (digits) {
unsigned long v = strtoul(s.c_str(), nullptr, 10);
if (v > std::numeric_limits<gid_t>::max()) return false;
out = static_cast<gid_t>(v);
return true;
}
if (group* gr = getgrnam(s.c_str())) { out = gr->gr_gid; return true; }
return false; // invalid group string
}

/**
* Change ownership of a USS file or directory (recursive optional).
*
* Supports "user", "user:group", ":group", or numeric IDs.
* Validates input, avoids silent -1, and returns RTNCD_FAILURE on error.
*/
int zusf_chown_uss_file_or_dir(ZUSF *zusf, std::string file, std::string owner, bool recursive)
{
struct stat file_stats;
// Verify target exists and capture current metadata
if (stat(file.c_str(), &file_stats) == -1)
{
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "Path '%s' does not exist", file.c_str());
return RTNCD_FAILURE;
}

// Refuse to descend into a directory if caller didn’t request recursion
if (S_ISDIR(file_stats.st_mode) && !recursive)
{
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "Path '%s' is a folder and recursive is false", file.c_str());
return RTNCD_FAILURE;
}

const auto uid = zusf_get_id_from_user_or_group(owner, true);
const auto colon_pos = owner.find_first_of(":");
const auto group = colon_pos != std::string::npos ? owner.substr(colon_pos + 1) : std::string();
const auto gid = group.empty() ? file_stats.st_gid : zusf_get_id_from_user_or_group(group, false);
const auto rc = chown(file.c_str(), uid, gid);
// Split owner into user[:group]
std::string userPart = owner;
std::string groupPart;
const auto colon_pos = owner.find(':');
if (colon_pos != std::string::npos) {
userPart = owner.substr(0, colon_pos);
groupPart = owner.substr(colon_pos + 1);
}

if (0 != rc)
{
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "chmod failed for path '%s', errno %d", file.c_str(), errno);
uid_t uid;
gid_t gid;

// Resolve user to UID (numeric or name); return error on invalid input
if (!resolve_uid_from_str(userPart, uid)) {
errno = EINVAL;
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "chown error: invalid user '%s'", userPart.c_str());
return RTNCD_FAILURE;
}

// Resolve group to GID (numeric or name); return error on invalid input
if (!resolve_gid_from_str(groupPart, gid)) {
errno = EINVAL;
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "chown error: invalid group '%s'", groupPart.c_str());
return RTNCD_FAILURE;
}

// If both were empty, refuse (otherwise chown(-1,-1) is a no-op)
if (uid == (uid_t)-1 && gid == (gid_t)-1) {
errno = EINVAL;
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "chown error: neither user nor group specified");
return RTNCD_FAILURE;
}

// Preserve current group explicitly if only user was supplied
if (gid == (gid_t)-1) gid = file_stats.st_gid;

// Attempt chown
const auto rc = chown(file.c_str(), uid, gid);
if (rc != 0) {
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "chown failed for path '%s', errno %d", file.c_str(), errno);
return RTNCD_FAILURE;
}

// Recurse into directories if requested
if (recursive && S_ISDIR(file_stats.st_mode))
{
DIR *dir;
if ((dir = opendir(file.c_str())) == nullptr)
DIR *dir = opendir(file.c_str());
if (!dir)
{
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "Could not open directory '%s'", file.c_str());
return RTNCD_FAILURE;
}

struct dirent *entry;
while ((entry = readdir(dir)) != nullptr)
{
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
{
const string child_path = file[file.length() - 1] == '/' ? file + string((const char *)entry->d_name)
: file + string("/") + string((const char *)entry->d_name);
struct stat file_stats;
stat(child_path.c_str(), &file_stats);
const string child_path =
(file.length() > 0 && file[file.length() - 1] == '/') ? file + string(entry->d_name)
: file + string("/") + string(entry->d_name);

const auto rc = zusf_chown_uss_file_or_dir(zusf, child_path, owner, S_ISDIR(file_stats.st_mode));
if (0 != rc)
struct stat child_stats;
if (stat(child_path.c_str(), &child_stats) == -1)
{
return rc;
closedir(dir);
zusf->diag.e_msg_len = sprintf(zusf->diag.e_msg, "Path '%s' no longer accessible", child_path.c_str());
return RTNCD_FAILURE;
}

// Propagate chown to children, recursing into subdirectories
const auto child_rc =
zusf_chown_uss_file_or_dir(zusf, child_path, owner, S_ISDIR(child_stats.st_mode));
if (child_rc != 0)
{
closedir(dir);
return child_rc;
}
}
}
closedir(dir);
}

return 0;
}

int zusf_chtag_uss_file_or_dir(ZUSF *zusf, string file, string tag, bool recursive)
int zusf_chtag_uss_file_or_dir(ZUSF *zusf, std::string file, std::string tag, bool recursive)
{
struct stat file_stats;
if (stat(file.c_str(), &file_stats) == -1)
Expand Down
4 changes: 2 additions & 2 deletions native/c/zusf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ int zusf_write_to_uss_file(ZUSF *zusf, const std::string &file, std::string &dat
int zusf_write_to_uss_file_streamed(ZUSF *zusf, const std::string &file, const std::string &pipe, size_t *content_len);
int zusf_chmod_uss_file_or_dir(ZUSF *zusf, std::string file, mode_t mode, bool recursive);
int zusf_delete_uss_item(ZUSF *zusf, std::string file, bool recursive);
int zusf_chown_uss_file_or_dir(ZUSF *zusf, std::string file, const std::string &owner, bool recursive);
short zusf_get_id_from_user_or_group(const std::string &user_or_group, bool is_user);
int zusf_chown_uss_file_or_dir(ZUSF *zusf, std::string file, std::string owner, bool recursive);
int zusf_chtag_uss_file_or_dir(ZUSF *zusf, std::string file, std::string tag, bool recursive);
short zusf_get_id_from_user_or_group(const std::string &user_or_group, bool is_user);
int zusf_get_file_ccsid(ZUSF *zusf, std::string file);
std::string zusf_get_ccsid_display_name(int ccsid);
int zusf_get_ccsid_from_display_name(const std::string &display_name);
Expand Down
Loading