Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AIX/IBM i support #133

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@
"BRUTE_FORCE"
]
}],
# Depending on version of Python, IBM i can identify as AIX or OS400
['OS=="aix" or OS=="os400"', {
"sources": [
"src/watchman/BSER.cc",
"src/watchman/WatchmanBackend.cc",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the slow response on this PR.

One problem is that I don't think watchman is supported on AIX or OS400. There is also no inotify support as far as I can tell (I don't have access to a system to test this). Without either of those, this module isn't very useful (can't actually watch files). Do you know if these OSes have another file watching mechanism?

I'm also somewhat concerned about adding these as I have no way of testing or providing precompiled builds for these platforms. Do you have access to such a system to test with?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the late response.

Watchman isn't supported, but I didn't disable it, because Watchman is an external tool - I was thinking that it might support it or an alternative with a compatible interface might. It might be a good idea to disable it though.

It's using the "legacy" backend which is brute force; AFAIK there is no watching API, but I might be wrong. I can ask my contacts at IBM.

I understand the concerns about not being able to test things. I do have access to a system for testing. FWIW not having a binary build is OK; the user I'm doing this for is already used to having the dev toolchain to build native parts already.

(FWIW, the motivation to upstream this was things depending on watcher being extremely common in some ecosystems. Finding and patching every dependency to pull a patched copy was too onerous.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting a ping out here - would like to know if the above is still blocking.

"src/shared/BruteForceBackend.cc",
"src/unix/legacy.cc",
"src/unix/atshims.cc"
],
"defines": [
"WATCHMAN",
"BRUTE_FORCE"
],
# Required for threaded parts of stdc++
'ldflags': [
'-pthread',
],
'cflags': [
'-pthread',
],
}],
['OS=="win"', {
"sources": [
"src/watchman/BSER.cc",
Expand Down
362 changes: 362 additions & 0 deletions src/unix/atshims.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
#ifdef __PASE__
/*
* IBM i has defective *at functions (only supports AT_FDCWD). These shims were
* provided to me from IBM, developed for some other software.
*
* The shim had openat, unlinkat, and renameat. We need openat and fstatat, so
* the shim has been modified to provide this set of functions.
*
* These were also provided to me so that they could be relicensed under the
* same license that Watcher is under.
*
* Copyright (c) 2022-2023 IBM Corporation
*/

/* This code begins */
/* */
/* File............: atfuncs.cinc */
/* Purpose.........: Provide PASE for i alternative functions for unlinkat1, openat, and renameat.*/
/* */
/* Usage Notes.....: These functions work in the following way: */
/* (1) If the path is absolute (i.e. starts with '/'), pass the parameters to */
/* the non-alternative function (i.e. openat2 -> open, etc.). */
/* (2) If the 'AT_FDCWD' flag is specified as the directory file descriptor, */
/* pass the parameters to the non-alternative function. This usage */
/* relies on underlying support for relative paths in the non-alternative */
/* functions. Note that for renameat, both files descriptors must be */
/* supplied the 'AT_FDCWD' flag to perform this usage. */
/* (3) For openat and unlinkat, if neither (1) or (2) are performed, then */
/* the process's current working directory is generated, and then changed */
/* to the supplied directory file descriptor and the non-alternative */
/* function is called using relative pathnames like (2). For renameat, */
/* the current working directory is generated. For renameat, if a valid */
/* (i.e. not 'AT_FDCWD') is directory file descriptor is supplied, an */
/* absolute file path is generated for that filename. */

#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#include <dirent.h>

/* Function.......: getcwdpath */
/* Purpose........: This function will get the current working directory up to the maximum */
/* pathname length. */
/* */
/* Inputs.........: const char *, unsigned int, char **, int * */
/* */
/* buf1(output)....: A pointer to a character buffer that that the current working directory */
/* MAY be placed in. The buffer pointer to is typically allocated from stack */
/* storage. */
/* buf1Size(input): The size in bytes of buf1. */
/* buf2(output)...: A pointer to a pointer to a character buffer that MAY point to allocated */
/* storage during this function invocation. The allocated storage will be from */
/* the heap storage. The caller is responsible to free this storage. */
/* cwdPathLoc(output): A pointer to an integer which determines where the current working */
/* directory output was placed. */
/* The integer has the following possible values: */
/* 0 - the output was placed in buf1. */
/* 1 - the output was placed in buf2. This storage was malloc'd and must */
/* be freed by the caller. */
/* */
/* Return Value....: 0 - success */
/* -1 - failure, check errno for more information */
/* */
/* Notes...........: None */
int getcwdpath(const char * buf1, unsigned int buf1Size,
char ** buf2, unsigned int * cwdPathLoc)
{
if((buf1 == (const char *)NULL) || /* safety/sanity check */
(cwdPathLoc == (unsigned int *)NULL))
{
errno = EINVAL;
return -1;
}

int curpathrc = 0; /* default to success. */
*cwdPathLoc = 0; /* default to passed in buffer. */

/* first try to get the cwd path using the supplied buffer */
if(getcwd((char *)buf1, buf1Size) == NULL)
{
/* failed to get the current working directory. */
/* check if the failure was because the input buffer was too small. */

/* Note that this code exists, but apparently AIX's PATH_MAX limit */
/* is 512 bytes which is below the 4096 that is used for the stack */
/* buffer in the at-functions. This code will never be executed on */
/* AIX. See IBM Documenation for AIX 7.3 for the limits.h header */
/* for more information: */
/* https://www.ibm.com/docs/en/aix/7.3?topic=files-limitsh-file */

if((errno == ERANGE) ||
(errno == E2BIG))
{
unsigned int buf2Size = buf1Size;
char * bp = NULL;
do
{
buf2Size *= 2; /* double the buffer size */
if(buf2Size > PATH_MAX) /* if the buffer size is above */
buf2Size = PATH_MAX; /* the max, set size to max */

if(bp != NULL)
free((void *)bp); /* free previous buffer */

bp = (char *)malloc(buf2Size); /* malloc new storage */
if(bp == NULL)
{
/* errno will be set by malloc() failure. */
/* Nothing was malloc'd so nothing to free before returning. */
curpathrc = -1;
break;
}
/* loop while: */
/* getcwd() fails with errno ERANGE or E2BIG, and the current */
/* size of the buffer is less than the max path length. */
} while((getcwd(bp, buf2Size) != NULL) &&
((errno == ERANGE) || (errno == E2BIG)) &&
(buf2Size < PATH_MAX));

*buf2 = bp; /* set output buffer to newly malloc'd space */
*cwdPathLoc = 1; /* set output indicator */
}
else
{
/* getcwd() didn't fail because the buffer was too small. */
/* errno will be set by getcwd() failure. */
curpathrc = -1;
}
}
/* else */
/* all the work was done by the call to cwd, and the output should */
/* be in buf1. */

return curpathrc;
}

int ibmi_fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags)
{
int _fstatrc = -1; /* default to fail */
const unsigned int cwdBufSize = 4096; /* cwd init buffer size */
char cwdbuf[cwdBufSize]; /* cwd init buffer */
char ** cwdpath = NULL; /* cwd malloc'd path */
unsigned int cwdPathLoc = 0; /* cwd path location */

/* AT_SYMLINK_NOFOLLOW indicates if lstat or stat */
int (*underfunc)(const char *, struct stat *) = NULL;

if(pathname == (const char *)NULL) /* safety/sanity check */
{
errno = EINVAL;
return -1;
}

if(flags & AT_SYMLINK_NOFOLLOW) /* stat on a symbolic link */
underfunc = lstat;
else /* stat on a file */
underfunc = stat;

/* path is absolute, stat it. */
if(*pathname == '/')
return underfunc(pathname, statbuf);

/* path is relative, stat in current working directory of process. */
if(dirfd == AT_FDCWD)
return underfunc(pathname, statbuf);

/* path is relative, but caller doesn't want to use process's current */
/* working directory. Build the current working directory, so it */
/* can be changed back to. */
_fstatrc = getcwdpath((const char *)&cwdbuf, cwdBufSize,
cwdpath, &cwdPathLoc);

if(_fstatrc == -1)
{
/* errno will be set by getcwdpath() failure. */
_fstatrc = -1;
goto leave;
}

/* path is relative, change process's current working directory to */
/* the directory file descriptor. */
if(fchdir(dirfd) == -1)
{
/* errno will be set by fchdir() failure. */
_fstatrc = -1;
goto leave;
}

_fstatrc = underfunc(pathname, statbuf);

/* change working directory back to original process's working */
/* directory. */
if(cwdPathLoc == 0)
{
if(chdir(cwdbuf) == -1)
{
/* errno will be set by chdir() failure. */
_fstatrc = -1;
}
}
else
{
if(chdir(*cwdpath) == -1)
{
/* errno will be set by chdir() failure. */
_fstatrc = -1;
}
}

leave:
if(*cwdpath != NULL) /* if malloc'd storage, free it now. */
free(*cwdpath);

return _fstatrc;
}

int ibmi_openat(int dirfd, const char *pathname, int flags)
{
int _openrc = -1; /* default to fail */
const unsigned int cwdBufSize = 4096; /* cwd init buffer size */
char cwdbuf[cwdBufSize]; /* cwd init buffer */
char ** cwdpath = NULL; /* cwd malloc'd path */
unsigned int cwdPathLoc = 0; /* cwd path location */

if(pathname == (const char *)NULL) /* safety/sanity check */
{
errno = EINVAL;
return -1;
}

/* path is absolute, open it. */
if(*pathname == '/')
return open(pathname, flags);

/* path is relative, open in current working directory of process. */
if(dirfd == AT_FDCWD)
return open(pathname, flags);

/* path is relative, but caller doesn't want to use process's current */
/* working directory. Build the current working directory, so it */
/* can be changed back to. */
_openrc = getcwdpath((const char *)&cwdbuf, cwdBufSize,
cwdpath, &cwdPathLoc);

if(_openrc == -1)
{
/* errno will be set by getcwdpath() failure. */
return -1;
}

/* path is relative, change process's current working directory to */
/* the directory file descriptor. */
if(fchdir(dirfd) == -1)
{
_openrc = -1;
goto leave;
}

/* path is relative, open it relative to directory file descriptor. */
_openrc = open(pathname, flags);

/* change working directory back to original process's working */
/* directory. */
if(cwdPathLoc == 0)
{
if(chdir(cwdbuf) == -1)
{
/* errno will be set by chdir() failure. */
_openrc = -1;
}
}
else
{
if(chdir(*cwdpath) == -1)
{
/* errno will be set by chdir() failure. */
_openrc = -1;
}
}

leave:
if(*cwdpath != NULL) /* if malloc'd storage, free it now. */
free(*cwdpath);

return _openrc;
}

int ibmi_openat2(int dirfd, const char * pathname, int flags, mode_t mode)
{
int _openrc = -1; /* default to fail */
const unsigned int cwdBufSize = 4096; /* cwd init buffer size */
char cwdbuf[cwdBufSize]; /* cwd init buffer */
char ** cwdpath = NULL; /* cwd malloc'd path */
unsigned int cwdPathLoc = 0; /* cwd path location */

if(pathname == (const char *)NULL) /* safety/sanity check */
{
errno = EINVAL;
return -1;
}

/* path is absolute, open it. */
if(*pathname == '/')
return open(pathname, flags, mode);

/* path is relative, open in current working directory of process. */
if(dirfd == AT_FDCWD)
return open(pathname, flags, mode);

/* path is relative, but caller doesn't want to use process's current */
/* working directory. Build the current working directory, so it */
/* can be changed back to. */
_openrc = getcwdpath((const char *)&cwdbuf, cwdBufSize,
cwdpath, &cwdPathLoc);

if(_openrc == -1)
{
/* errno will be set by getcwdpath() failure. */
return -1;
}

/* path is relative, change process's current working directory to */
/* the directory file descriptor. */
if(fchdir(dirfd) == -1)
{
_openrc = -1;
goto leave;
}

/* path is relative, open it relative to directory file descriptor. */
_openrc = open(pathname, flags, mode);

/* change working directory back to original process's working */
/* directory. */
if(cwdPathLoc == 0)
{
if(chdir(cwdbuf) == -1)
{
/* errno will be set by chdir() failure. */
_openrc = -1;
}
}
else
{
if(chdir(*cwdpath) == -1)
{
/* errno will be set by chdir() failure. */
_openrc = -1;
}
}

leave:
if(*cwdpath != NULL) /* if malloc'd storage, free it now. */
free(*cwdpath);

return _openrc;
}
#endif
Loading