/* -*- Mode: C; c-file-style: "gnu" -*-
   unixfilesystem.c -- native methods pertaining to java.io.UnixFileSystem
   Created: Chris Toshok <toshok@hungry.com>, 12-Nov-1998
 */
/*
  This file is part of Japhar, the GNU Virtual Machine for Java Bytecodes.
  Japhar is a project of The Hungry Programmers, GNU, and OryxSoft.

  Copyright (C) 1997, 1998, 1999 The Hungry Programmers

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Library General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "jni.h"
#include "common.h"
#include "exceptions.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif

/* On Solaris, include <limits.h> to pick up PATH_MAX */
#if defined(__sun__) && defined(__svr4__)
#include <limits.h>
#endif

static jint BA_EXISTS;
static jint BA_REGULAR;
static jint BA_DIRECTORY;
static jint BA_HIDDEN;

/**
 * WARNING: returned pointer must be freed by caller
 */
static char *
jstring2charptr(JNIEnv *env, jstring jstr)
{
  char *str_copy;
  const jbyte *str_chars;
  int str_length;
  str_chars = (*env)->GetStringUTFChars(env, jstr, NULL);
  str_length = (*env)->GetStringUTFLength(env, jstr) + 1;
  str_copy = (char*) malloc(str_length * sizeof(char));
  strncpy(str_copy, (char*)str_chars, str_length);
  (*env)->ReleaseStringUTFChars(env, jstr, str_chars);
  str_copy[str_length-1] = 0; /* Make sure the string is zero terminated */
  return str_copy;
}

/* JDK 1.2 */
JNIEXPORT jstring JNICALL
Java_java_io_UnixFileSystem_canonicalize(JNIEnv *env,
					 jobject fs,
					 jstring name)
{
  char *path_str = jstring2charptr(env, name);
  jstring ret_val;

#ifdef HAVE_RESOLVEPATH
  {
    char resulting_path[ PATH_MAX + 1 ];
    int len;

    len = resolvepath(path_str, resulting_path, sizeof(resulting_path));

    resulting_path[ len ] = 0;

    ret_val = (*env)->NewStringUTF(env, resulting_path);
  }
#else /* punt and don't transform the name at all */
  ret_val = name;
#endif

  free(path_str);

  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_checkAccess(JNIEnv *env,
					jobject fs,
					jobject file,
					jboolean check_for_write)
{
  char *path_name = get_file_path(env, file);
  struct stat buf;
  uid_t uid;
  gid_t gid;
  jboolean ret_val;

  uid = geteuid();
  gid = getegid();

  if (stat(path_name, &buf) == -1)
    {
      ret_val = JNI_FALSE;
      goto done;
    }

  if (check_for_write)
    {
      ret_val = ((uid == buf.st_uid && buf.st_mode & S_IWUSR)
		 || (gid == buf.st_gid && buf.st_mode & S_IWGRP)
		 || buf.st_mode & S_IXOTH);
    }
  else
    {
      ret_val = ((uid == buf.st_uid && buf.st_mode & S_IRUSR)
		 || (gid == buf.st_gid && buf.st_mode & S_IRGRP)
		 || buf.st_mode & S_IROTH);
    }

 done:
  free(path_name);
  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createDirectory(JNIEnv *env,
					    jobject fs,
					    jobject dir)
{
  char *path_str = get_file_path(env, dir);
  jboolean ret_val;

  if (0 != mkdir(path_str, 0777))
    ret_val = JNI_FALSE;
  else
    ret_val = JNI_TRUE;

  free(path_str);

  return ret_val;
}
			     
/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createFileExclusively(JNIEnv *env,
						  jobject fs,
						  jstring name)
{
  char *name_str = jstring2charptr(env, name);
  jboolean ret_val;

  if (-1 == open (name_str, O_CREAT | O_EXCL | O_TRUNC
#ifdef O_LARGEFILE
		  | O_LARGEFILE
#endif
		  , 0777))
    {
      /* error occurred */

      /* if the file already existed we don't throw an
	 exception - we just return FALSE;  */
      if (errno != EEXIST) 
	{
	  throw_Exception(env, "java/io/IOException", "error creating file");
	}

      ret_val = JNI_FALSE;
    }
  else
    {
      ret_val = JNI_TRUE;
    }

  free (name_str);
  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_delete(JNIEnv *env,
				   jobject fs,
				   jobject file)
{
  jboolean ret_val;
  char *path_str = get_file_path(env, file);

  if (0 != unlink(path_str))
    ret_val = JNI_FALSE;
  else
    ret_val = JNI_TRUE;

  free (path_str);

  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_deleteOnExit(JNIEnv *env,
					 jobject fs,
					 jobject file)
{
  (*env)->FatalError(env, "Java_java_io_UnixFileSystem_deleteOnExit not implemented");
  return JNI_FALSE;
}

/* JDK 1.2 */
JNIEXPORT jint JNICALL
Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv *env,
						  jobject fs,
						  jobject file)
{
  int r;
  int ret_val = 0;
  struct stat buf;
  char *path_name = NULL;

  r = stat_file(env, file, &buf);
  path_name = get_file_path(env, file);

  if (r == -1)
    {
      ret_val = 0;
      goto done;
    }

  ret_val |= BA_EXISTS;
  if (S_ISDIR(buf.st_mode))
    ret_val |= BA_DIRECTORY;
  else
    ret_val |= BA_REGULAR;

  if (path_name[0] == '.')
    ret_val |= BA_HIDDEN;

 done:
  if (path_name) free(path_name);
  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLastModifiedTime(JNIEnv *env,
						jobject fs,
						jobject file)
{
  int r;
  struct stat buf;

  r = stat_file(env, file, &buf);

  if (r == -1)
    return 0;
  else
    return (jlong)buf.st_mtime;
}

/* JDK 1.2 */
JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLength(JNIEnv *env,
				      jobject fs,
				      jobject file)
{
  int r;
  struct stat buf;

  r = stat_file(env, file, &buf);

  if (r == -1)
    return 0;
  else
    return (jlong)buf.st_size;
}

static int
scandir_select(struct dirent *entry)
{
  if (!strcmp(entry->d_name, ".")
      || !strcmp(entry->d_name, ".."))
    return 0;
  else
    return 1;
}

/* JDK 1.2 */
JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list(JNIEnv *env,
				 jobject fs,
				 jobject dir)
{
  char *path = get_file_path(env, dir);
  struct dirent **entries;
  int num_entries;
  jobjectArray elements;
  jclass string_class = (*env)->FindClass(env, "java/lang/String");
  int i;

  num_entries = scandir(path, &entries, scandir_select, NULL);

  if (num_entries == -1)
    {
      throw_Exception(env, "java/lang/OutOfMemory", "In java.io.UnixFileSystem.list");
      return NULL;
    }

  elements = (*env)->NewObjectArray(env, num_entries, string_class,
				    NULL);
  elements = (*env)->NewGlobalRef(env, elements);

  for (i = 0; i < num_entries; i ++)
    {
      jobject element = (*env)->NewStringUTF(env, entries[i]->d_name);

      element = (*env)->NewGlobalRef(env, element);

      (*env)->SetObjectArrayElement(env, elements, i, element);
    }

  free(entries);

  return elements;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_rename(JNIEnv *env,
				   jobject fs,
				   jobject file1,
				   jobject file2)
{
  char *path_str1 = get_file_path(env, file1);
  char *path_str2 = get_file_path(env, file2);
  jboolean ret_val;

  if (0 != rename(path_str1, path_str2))
    ret_val = JNI_FALSE;
  else
    ret_val = JNI_TRUE;

  free(path_str1);
  free(path_str2);

  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_setLastModifiedTime(JNIEnv *env,
						jobject fs,
						jobject file,
						jlong mtime)
{
  char *path_str = get_file_path(env, file);
  int r;
  struct stat buf;
  jboolean ret_val;

  r = stat_file(env, file, &buf);

  if (r == -1)
    {
      ret_val = JNI_FALSE;
    }
  else
    {
      struct timeval times[2];
      times[0].tv_sec = buf.st_atime;
      times[0].tv_usec = 0;

      /* XX is mtime microseconds? */
      times[1].tv_sec = mtime;
      times[1].tv_usec = 0;
      
      if (0 != utimes(path_str, times))
	ret_val = JNI_FALSE;
      else
	ret_val = JNI_TRUE;
    }
  
  free(path_str);

  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_setReadOnly(JNIEnv *env,
					jobject fs,
					jobject file)
{
  char *path_str;
  int r;
  struct stat buf;
  mode_t new_mode;
  jboolean ret_val = JNI_TRUE;

  r = stat_file(env, file, &buf);

  if (r == -1)
    return JNI_FALSE;

  new_mode = buf.st_mode;

  new_mode &= ~(S_IWRITE | S_IWGRP | S_IWOTH);

  path_str = get_file_path(env, file);

  if (chmod(path_str, new_mode) == -1)
    ret_val = JNI_FALSE;

  free (path_str);
  return ret_val;
}

/* JDK 1.2 */
JNIEXPORT void JNICALL
Java_java_io_UnixFileSystem_initIDs(JNIEnv *env,
				    jclass cls)
{
  jclass filesystem_cls = (*env)->FindClass(env, "java/io/FileSystem");
  jfieldID BA_EXISTS_field = (*env)->GetStaticFieldID(env, filesystem_cls, "BA_EXISTS", "I");
  jfieldID BA_REGULAR_field = (*env)->GetStaticFieldID(env, filesystem_cls, "BA_REGULAR", "I");
  jfieldID BA_DIRECTORY_field = (*env)->GetStaticFieldID(env, filesystem_cls, "BA_DIRECTORY", "I");
  jfieldID BA_HIDDEN_field = (*env)->GetStaticFieldID(env, filesystem_cls, "BA_HIDDEN", "I");

  BA_EXISTS = (*env)->GetStaticIntField(env, cls, BA_EXISTS_field);
  BA_REGULAR = (*env)->GetStaticIntField(env, cls, BA_REGULAR_field);
  BA_DIRECTORY = (*env)->GetStaticIntField(env, cls, BA_DIRECTORY_field);
  BA_HIDDEN = (*env)->GetStaticIntField(env, cls, BA_HIDDEN_field);
}

