Browse Source

Change nsmd to follow XDG Base Directory Specification. closes #gh-15 . Repair and extend lockfiles as well. fixes #gh-31

tags/v1.6.0
Nils 3 years ago
parent
commit
b19f458473
3 changed files with 151 additions and 48 deletions
  1. +10
    -24
      src/file.cpp
  2. +1
    -2
      src/file.h
  3. +140
    -22
      src/nsmd.cpp

+ 10
- 24
src/file.cpp View File

@@ -65,32 +65,18 @@ exists ( const char *name )
return 0 == stat( name, &st );
}

bool
acquire_lock ( int *lockfd, const char *filename )
{
struct flock fl;

fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;

*lockfd = ::creat( filename, 0777 );

if ( fcntl( *lockfd, F_SETLK, &fl ) != 0 )
return false;

return true;
}

void
release_lock ( int *lockfd, const char *filename )
{
unlink( filename );

::close( *lockfd );
char *
simple_hash( const char *s )
{ //djb2
unsigned long hashAddress = 5381;
for ( int counter = 0; s[counter]!='\0'; counter++ ) {
hashAddress = ( (hashAddress << 5) + hashAddress ) + s[counter];
}

*lockfd = 0;
char *result = NULL;
asprintf( &result, "%lu", hashAddress % 65521 );
return result;
}

int


+ 1
- 2
src/file.h View File

@@ -26,8 +26,7 @@ unsigned long modification_time ( const char *file );
bool newer ( const char *file1, const char *file2 );
unsigned long size ( const char *file );
int exists ( const char *name );
bool acquire_lock ( int *lockfd, const char *filename );
void release_lock ( int *lockfd, const char *filename );
char * simple_hash( const char *s);
int backwards_fgetc ( FILE *fp );
char * backwards_afgets ( FILE *fp );
void touch ( int fd );


+ 140
- 22
src/nsmd.cpp View File

@@ -62,13 +62,13 @@ static lo_address gui_addr;
static bool gui_is_active = false;
static int signal_fd;

static int session_lock_fd = 0;
static char *session_root;
static char *lockfile_directory;

#define NSM_API_VERSION_MAJOR 1
#define NSM_API_VERSION_MINOR 1
#define NSM_API_VERSION_PATCH 1
#define VERSION_STRING "1.5.3"
#define VERSION_STRING "1.6.0"

#define ERR_OK 0
#define ERR_GENERAL_ERROR -1
@@ -665,6 +665,7 @@ get_client_project_path ( const char *session_path, Client *c )
return client_project_path;
}


bool
launch ( const char *executable, const char *client_id )
{
@@ -1238,6 +1239,44 @@ command_client_to_quit ( Client *c )
}
}

char *
get_lock_file_name( const char * session_name )
{
char *session_root_hash = simple_hash( session_root ); // To avoid collisions of two nsmd running on different session_root with the same simple-named session.

char *session_lock;
asprintf( &session_lock, "%s/%s%s", lockfile_directory, session_name, session_root_hash ); //lockfile_directory and session_name are variables in the current context.

free(session_root_hash);
return session_lock;
}


void
write_lock_file( const char *filename, const char * session_path )
{
//Not a GNU lockfile, which features were never used by nsmd anyway,
//but simply a file with information about the NSM Server and the loaded session
FILE *fp = fopen( filename, "w" );

if ( !fp ) {
FATAL( "Failed to write lock file to %s with error: %s", filename, strerror( errno ) );
}

fprintf( fp, "%s\n%s\n%d\n", session_path, osc_server->url(), getpid());
MESSAGE( "Created lock file %s", filename );

fclose( fp );
}

void
delete_lock_file( const char *filename )
{
unlink( filename );
MESSAGE( "Deleted lock file %s", filename );
}


void
close_session ( )
{
@@ -1259,13 +1298,11 @@ close_session ( )

if ( session_path )
{
char *session_lock;
asprintf( &session_lock, "%s/.lock", session_path );

release_lock( &session_lock_fd, session_lock );
char * session_lock = get_lock_file_name(session_name);
delete_lock_file( session_lock );
MESSAGE( "Session %s was closed.", session_path );

free(session_lock);

free(session_path);
session_path = NULL;
free(session_name);
@@ -1305,17 +1342,29 @@ tell_all_clients_session_is_loaded ( void )
int
load_session_file ( const char * path )
{

if ( session_path && session_name ) {
//We are already in a session. This is switch, or load during duplicate etc.
MESSAGE ( "Instructed to load %s while %s is still open. This is a normal operation. Attempting to switch clients intelligently, if they support it. Otherwise closing and re-opening.", path, session_path );
char * session_lock = get_lock_file_name(session_name);
delete_lock_file( session_lock );
}

set_name( path ); //Do this first so we have the name for lockfiles and log messages

char *session_file = NULL;
asprintf( &session_file, "%s/session.nsm", path );
char *session_lock = NULL;
asprintf( &session_lock, "%s/.lock", path );

if ( ! acquire_lock( &session_lock_fd, session_lock ) )
//Check if the lockfile already exists, which means another nsmd currently has loaded the session we want to load.
char * session_lock = get_lock_file_name(session_name);
struct stat st_lockfile_exists_check;
if ( stat (session_lock, &st_lockfile_exists_check) == 0)
{
WARNING( "Session %s is already loaded from another nsmd and locked by file %s", session_name, session_lock );

free( session_file );
free( session_lock );

WARNING( "Session is locked by another process" );
return ERR_SESSION_LOCKED;
}

@@ -1332,7 +1381,6 @@ load_session_file ( const char * path )

session_path = strdup( path );

set_name( path );

std::list<Client*> new_clients;

@@ -1456,7 +1504,11 @@ load_session_file ( const char * path )

tell_all_clients_session_is_loaded();

MESSAGE( "Loaded." );
//We already checked if the logfile exists above, and it didn't.
//We also tested for write permissions to our XDG run-dir, which we confirmed to have.
//We can create the lockfile now.
write_lock_file( session_lock, session_path );
MESSAGE( "Session %s was loaded.", session_path);

new_clients.clear();

@@ -1567,7 +1619,9 @@ OSC_HANDLER( duplicate )

osc_server->send( gui_addr, "/nsm/gui/session/session", &argv[0]->s );

MESSAGE( "Attempting to open %s", spath );
MESSAGE( "Attempting to open during DUPLICATE: %s", spath );

//The original session is still open. load_session_file will close it, and possibly ::switch::

if ( !load_session_file( spath ) )
{
@@ -1653,6 +1707,11 @@ OSC_HANDLER( new )

set_name( session_path );

char * session_lock = get_lock_file_name(session_name);
write_lock_file( session_lock, session_path );
free ( session_lock );


osc_server->send( sender_addr, "/reply", path, "Created." );

if ( gui_is_active )
@@ -2474,7 +2533,8 @@ handle_signal_clean_exit ( int signal )
MESSAGE( "Caught SIGNAL %i. Stopping nsmd.", signal);
// We want a clean exit even when things go wrong.
close_session();
free(session_root);
free( session_root );
free( lockfile_directory );
exit(0);
}

@@ -2572,7 +2632,7 @@ int main(int argc, char *argv[])
" --help Show this screen\n"
" --version Show version\n"
" --osc-port portnum OSC port number [Default: provided by system].\n"
" --session-root path Base path for sessions [Default: ~/NSM Sessions].\n"
" --session-root path Base path for sessions [Default: $XDG_DATA_HOME/nsm/].\n"
" --load-session name Load existing session [Example: \"My Song\"].\n"
" --gui-url url Connect to running legacy-gui [Example: osc.udp://mycomputer.localdomain:38356/].\n"
" --detach Detach from console.\n"
@@ -2587,19 +2647,77 @@ int main(int argc, char *argv[])
}
}

if ( !session_root )

//Get the XDG runtime directory for lockfiles
//https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
//Unlike $XDG_DATA_HOME the runtime env var must be set, usually to /run/user/1000/
struct stat rundir_check;
lockfile_directory = getenv( "XDG_RUNTIME_DIR" );
if ( stat( lockfile_directory, &rundir_check ) != 0 && S_ISDIR(rundir_check.st_mode)) {
FATAL( "Failed to access $XDG_RUNTIME_DIR directory %s with error: %s", lockfile_directory, strerror( errno ) );
}
else {
asprintf( &lockfile_directory, "%s/%s", lockfile_directory, "nsm");

//Create the 'nsm' subdirectory. This may fail on it's own.
struct stat st_lockfile_dir_mkdir;
if ( stat( lockfile_directory, &st_lockfile_dir_mkdir ) )
{
if ( mkdir( lockfile_directory, 0771 ) )
{
FATAL( "Failed to create lock file directory %s with error: %s", lockfile_directory, strerror( errno ) );
}
}
MESSAGE( "Using %s for lock-files.", lockfile_directory );
}



if ( !session_root ) {
/* The user gave no specific session directory. We use the default.
* The default dir follows the XDG Basedir Specifications:
* It is used by looking up environment variables.
* https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
* $XDG_DATA_HOME defines the base directory relative to which user-specific data files
* should be stored. If $XDG_DATA_HOME is either not set or empty, a default equal to
* $HOME/.local/share should be used.
*
* Up to version 1.5.3 the default dir was ~/NSM Sessions .
* If this old directory exists we will use it but write mild warning to the log.
* Moving old sessions is left to the user, or an external GUI.
*/
struct stat st_session_root;

asprintf( &session_root, "%s/%s", getenv( "HOME" ), "NSM Sessions" );
if ( stat( session_root, &st_session_root ) == 0 && S_ISDIR(st_session_root.st_mode)) {
WARNING ( "An old session directory was detected in %s. You can continue to use it but it is recommended to move your sessions to $XDG_DATA_HOME/nsm-sessions/. If you don't know where that is simply rename your current session-directory and start nsmd, which will tell you the new directory.", session_root);
}
else {
const char *xdg_data_home = getenv( "XDG_DATA_HOME" );
if ( xdg_data_home ) {
//If $XDG_DATA_HOME is explicitly set by the user we assume it to exist. We don't want to recursively create system directories.
//If the xdg-dir does not exist yet we FATAL out just below.
asprintf( &session_root, "%s/%s", xdg_data_home, "nsm" );
}
else {
//If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
asprintf( &session_root, "%s/.local/share/nsm", getenv( "HOME" ));
}
}
}

struct stat st;
struct stat st_session_root_mkdir;

if ( stat( session_root, &st ) )
if ( stat( session_root, &st_session_root_mkdir ) )
{
if ( mkdir( session_root, 0771 ) )
{
FATAL( "Failed to create session directory: %s", strerror( errno ) );
FATAL( "Failed to create session directory %s with error: %s", session_root, strerror( errno ) );
}
}



MESSAGE( "Session root is: %s", session_root );

osc_server = new OSC::Endpoint();
@@ -2660,7 +2778,7 @@ int main(int argc, char *argv[])
{
char *spath;
asprintf( &spath, "%s/%s", session_root, load_session); // Build the session path. --load-session works with --session-root
MESSAGE( "LOAD SESSION %s", spath);
MESSAGE( "Loading session given by parameter %s", spath);
load_session_file( spath );
free ( spath );
}
@@ -2697,7 +2815,7 @@ int main(int argc, char *argv[])
//Without a signal handler clients will remain active ("zombies") without nsmd as parent.
//MESSAGE ( "End of Program");

//free(session_root);// This was not executed if nsmd received a stop signal. It is now handled by doExit()
//free(session_root);// This was not executed if nsmd received a stop signal. It is now handled by handle_signal_clean_exit()
//osc_server->run();

return 0;


Loading…
Cancel
Save