Logo Search packages:      
Sourcecode: shntool version File versions  Download package

core_misc.c

/*  core_misc.c - miscellaneous functions
 *  Copyright (C) 2000-2004  Jason Jordan <shnutils@freeshell.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * $Id: core_misc.c,v 1.74 2004/04/14 22:01:44 jason Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include "shntool.h"
#include "wave.h"
#include "fileio.h"
#include "misc.h"
#include "binary.h"
#include "format.h"
#include "convert.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define ID3V2_MAGIC "ID3"

typedef struct {
  char magic[3];
  unsigned char version[2];
  unsigned char flags[1];
  unsigned char size[4];
} _id3v2_header;

char *child_args[MAX_CHILD_ARGS];
int num_child_args = 0;
int clobberflag = CLOBBER_ACTION_ALWAYS;

unsigned long check_for_id3v2_tag(FILE *f)
{
  _id3v2_header id3v2_header;
  unsigned long tag_size;

  /* read an ID3v2 header's size worth of data */
  if (sizeof(_id3v2_header) != fread(&id3v2_header,1,sizeof(_id3v2_header),f)) {
    return 0;
  }

  /* verify this is an ID3v2 header */
  if (tagcmp(id3v2_header.magic,ID3V2_MAGIC) ||
      0xff == id3v2_header.version[0] || 0xff == id3v2_header.version[1] ||
      0x80 <= id3v2_header.size[0] || 0x80 <= id3v2_header.size[1] ||
      0x80 <= id3v2_header.size[2] || 0x80 <= id3v2_header.size[3])
  {
    return 0;
  }

  /* calculate and return ID3v2 tag size */
  tag_size = synchsafe_int_to_ulong(id3v2_header.size);

  return tag_size;
}

FILE *open_input_internal(char *filename,int *file_has_id3v2_tag,wlong *id3v2_tag_size)
/* opens a file, and if it contains an ID3v2 tag, skips past it */
{
  FILE *f;
  unsigned long tag_size;

  if (NULL == (f = fopen(filename,"rb"))) {
    return NULL;
  }

  if (file_has_id3v2_tag)
    *file_has_id3v2_tag = 0;

  if (id3v2_tag_size)
    *id3v2_tag_size = 0;

  /* check for ID3v2 tag on input */
  if (0 == (tag_size = check_for_id3v2_tag(f))) {
    fclose(f);
    return fopen(filename,"rb");
  }

  if (file_has_id3v2_tag)
    *file_has_id3v2_tag = 2;

  if (id3v2_tag_size)
    *id3v2_tag_size = (wlong)(tag_size + sizeof(_id3v2_header));

  st_debug("discarding %lu-byte ID3v2 tag at beginning of file '%s'",tag_size+sizeof(_id3v2_header),filename);

  if (0 != fseek(f,(long)tag_size,SEEK_CUR)) {
    st_warning("error while discarding ID3v2 tag in file '%s'",filename);
    fclose(f);
    return fopen(filename,"rb");
  }

  return f;
}

FILE *open_output(char *filename)
{
  return fopen(filename,"wb");
}

int open_input_stream(wave_info *info)
/* opens an input stream, and if it contains an ID3v2 tag, skips past it */
{
  unsigned long bytes_to_read,tag_size;
  char tmp[BUF_SIZE];

  if (info->file_has_id3v2_tag > 1) {
    if (info->input_format->decoder)
      st_debug("ID3v2 tag detected in file '%s' - decoder program '%s' might fail to process it",info->filename,info->input_format->decoder);
    else
      st_debug("ID3v2 tag detected in file '%s' - discarding.",info->filename);
    info->file_has_id3v2_tag = 1;
  }

  if (NULL == (info->input = info->input_format->input_func(info->filename,&info->input_pid))) {
    st_warning("could not open file '%s' for streaming input",info->filename);
    return 1;
  }

  /* check for ID3v2 tag on input stream */
  if (0 != (tag_size = check_for_id3v2_tag(info->input))) {
    if (0 == info->stream_has_id3v2_tag) {
      if (info->input_format->decoder)
        st_debug("ID3v2 tag detected in input stream generated by '%s' from file '%s' - discarding.",info->input_format->decoder,info->filename);
      else
        st_debug("ID3v2 tag detected in input stream from file '%s' - discarding.",info->filename);
      info->stream_has_id3v2_tag = 1;
    }

    if (0 == info->id3v2_tag_size)
      info->id3v2_tag_size = (wlong)(tag_size + sizeof(_id3v2_header));

    st_debug("discarding %lu-byte ID3v2 tag in input stream from file '%s' (generated by %s format decoder '%s')",
      info->id3v2_tag_size,info->filename,info->input_format->name,info->input_format->decoder);

    while (tag_size > 0) {
      bytes_to_read = min(tag_size,BUF_SIZE);

      if (bytes_to_read != read_n_bytes(info->input,tmp,bytes_to_read)) {
        if (info->input_format->decoder)
          st_warning("error while discarding ID3v2 tag in input stream generated by '%s' from file '%s'",info->input_format->decoder,info->filename);
        else
          st_warning("error while discarding ID3v2 tag in input stream from file '%s'",info->filename);
        close_input_stream(info);
        return 1;
      }

      tag_size -= bytes_to_read;
    }
  }
  else {
    close_input_stream(info);

    if (NULL == (info->input = info->input_format->input_func(info->filename,&info->input_pid))) {
      st_warning("could not re-open file '%s' for streaming input",info->filename);
      return 1;
    }
  }

  return 0;
}

int clobber_ask(char *filename)
{
  char response[BUF_SIZE];

  fprintf(stderr,"\n");

  while (1) {
    fprintf(stderr,"file '%s' already exists, overwrite ([Y]es, [N]o, [A]lways, ne[V]er)? ",filename);
    fgets(response,BUF_SIZE-1,stdin);
    response[strlen(response)-1] = '\0';

    if (0 == strcmp(response,"y") || 0 == strcmp(response,"Y"))
      return CLOBBER_FILE_YES;
    else if (0 == strcmp(response,"n") || 0 == strcmp(response,"N"))
      return CLOBBER_FILE_NO;
    else if (0 == strcmp(response,"a") || 0 == strcmp(response,"A")) {
      clobberflag = CLOBBER_ACTION_ALWAYS;
      return CLOBBER_FILE_YES;
    }
    else if (0 == strcmp(response,"v") || 0 == strcmp(response,"V")) {
      clobberflag = CLOBBER_ACTION_NEVER;
      return CLOBBER_FILE_NO;
    }
  }
}

int clobber_check(char *filename)
/* function to check if a file name is about to be clobbered, and if so, asks whether this is OK */
{
  struct stat sz;
  int retcode = CLOBBER_FILE_NO;

  if (stat(filename,&sz)) {
    /* file doesn't exist, so it's ok to clobber it.  ;)  */
    return CLOBBER_FILE_YES;
  }

  switch (clobberflag) {
    case CLOBBER_ACTION_ASK:
      retcode = clobber_ask(filename);
      break;
    case CLOBBER_ACTION_ALWAYS:
      retcode = CLOBBER_FILE_YES;
      break;
    case CLOBBER_ACTION_NEVER:
      retcode = CLOBBER_FILE_NO;
      break;
  }

  /* remove file if it exists, in case encoders check for its existence */
  if (CLOBBER_FILE_YES == retcode)
    remove_file(NULL,filename);

  return retcode;
}

void remove_file(format_module *fmt,char *filename)
{
  struct stat sz;
  int retval;

  if (fmt) {
    if (0 == strcmp(fmt->name,"cust")) {
      st_warning("partially-written custom format output file was not removed");
      return;
    }

    if (0 == strcmp(fmt->name,"null"))
      return;
  }

  if (stat(filename,&sz)) {
    st_debug("tried to remove nonexistent file '%s'",filename);
    return;
  }

  if (0 != (retval = unlink(filename))) {
    if (fmt) {
      st_warning("error while trying to remove partially-written output file '%s'",filename);
    }
  }

  st_debug("successfully removed file '%s'",filename);
}

int files_are_identical(char *file1,char *file2)
{
  struct stat sz1,sz2;

  if (stat(file1,&sz1) || stat(file2,&sz2))
    return 0;

  if (0 == S_ISREG(sz1.st_mode) || 0 == S_ISREG(sz2.st_mode))
    return 0;

  if (sz1.st_dev != sz2.st_dev)
    return 0;

  if (sz1.st_ino != sz2.st_ino)
    return 0;

  return 1;
}

int odd_sized_data_chunk_is_null_padded(wave_info *info)
/* function to determine whether odd-sized data chunks are NULL-padded to an even length */
{
  FILE *devnull;
  unsigned char nullpad[BUF_SIZE];

  if (0 == PROB_ODD_SIZED_DATA(info))
    return 1;

  /* with no extra RIFF chunks, we can tell from the extra RIFF size whether it's padded */
  if (-1 == info->extra_riff_size)
    return 0;

  if (0 == info->extra_riff_size)
    return 1;

  /* it's odd-sized, and has extra RIFF chunks, so we'll have to make a pass through it to know for sure.
   * most modes don't need to do this, but ones that update chunk sizes on existing files need to know whether
   * it's padded in order to calculate the correct chunk size (currently this includes pad and strip modes).
   */

  if (NULL == (devnull = open_output("/dev/null"))) {
    return 0;
  }

  if (0 != open_input_stream(info)) {
    fclose(devnull);
    return 0;
  }

  st_debug("scanning WAVE contents to determine whether odd-sized data chunk is padded with a NULL byte, per RIFF specs");

  if (info->header_size + info->data_size != transfer_n_bytes_quiet(info->input,devnull,info->header_size + info->data_size)) {
    fclose(devnull);
    close_input_stream(info);
    return 0;
  }

  nullpad[0] = 1;

  if (0 == read_n_bytes_quiet(info->input,nullpad,1)) {
    fclose(devnull);
    close_input_stream(info);
    return 0;
  }

  fclose(devnull);
  close_input_stream(info);

  st_debug("odd-sized data chunk in file '%s' is%s padded with a NULL byte",info->filename,(0==nullpad[0])?"":" not");

  return (0 == nullpad[0]) ? 1 : 0;
}

void tagcpy(char *dest,char *src)
/* simple copy of a tag from src to dest.  NOTE: src MUST be NULL-terminated,
 * and the trailing NULL byte is NOT copied.  This routine assumes that dest
 * is large enough to contain src.
 */
{
  int i;

  for (i=0;*(src+i);i++)
    *(dest+i) = *(src+i);
}

int tagcmp(char *got,char *expected)
/* compare got against expected, up to the length of expected */
{
  int i;

  for (i=0;*(expected+i);i++) {
    if (*(got+i) != *(expected+i))
      return i+1;
  }

  return 0;
}

void my_snprintf(char *dest,int maxlen,char *formatstr, ...)
/* acts like snprintf, but makes 100% sure the string is NULL-terminated */
{
  va_list args;

  va_start(args,formatstr);

  my_vsnprintf(dest,maxlen,formatstr,args);

  dest[maxlen-1] = 0;

  va_end(args);
}

void length_to_str(wave_info *info)
/* converts length of file to a string in m:ss or m:ss.ff format */
{
  wlong newlength,rem1,rem2,frames,ms;
  double tmp;

  if (PROB_NOT_CD(info)) {
    newlength = (wlong)info->exact_length;

    tmp = info->exact_length - (double)((wlong)info->exact_length);
    ms = (wlong)((tmp * 1000.0) + 0.5);

    if (1000 == ms) {
      ms = 0;
      newlength++;
    }

    my_snprintf(info->m_ss,16,"%lu:%02lu.%03lu",newlength/60,newlength%60,ms);
  }
  else {
    newlength = info->length;

    rem1 = info->data_size % CD_RATE;
    rem2 = rem1 % CD_BLOCK_SIZE;

    frames = rem1 / CD_BLOCK_SIZE;
    if (rem2 >= (CD_BLOCK_SIZE / 2))
      frames++;

    if (frames == CD_BLOCKS_PER_SEC) {
      frames = 0;
      newlength++;
    }

    my_snprintf(info->m_ss,16,"%lu:%02lu.%02lu",newlength/60,newlength%60,frames);
  }
}

int filename_contains_a_dot(char *filename)
/* determines whether a filename contains a '.' in its base file name */
{
  char *slash,*dot;

  dot = strrchr(filename,'.');
  if (NULL == dot)
    return 0;

  slash = strrchr(filename,'/');
  if (NULL == slash)
    return 1;

  if (slash < dot)
    return 1;

  return 0;
}

#ifndef HAVE_SETLINEBUF
int setlinebuf(FILE *fp)
{
  (void) setvbuf(fp,
#ifdef SETVBUF_REVERSED
                 _IOLBF,NULL,
#else
                 NULL,_IOLBF,
#endif
                 0);
  return 0;
}
#endif

int close_and_wait(FILE *fd,int pid,int child_type)
{
  int gotpid,status,retval = CLOSE_SUCCESS;
  char tmp[BUF_SIZE],debuginfo[BUF_SIZE];

  if (fd) {
    fclose(fd);
    fd = NULL;
  }

  strcpy(debuginfo,"");

  if (NO_CHILD_PID != pid) {
    my_snprintf(tmp,BUF_SIZE,"waiting for %s process %d ... ",(CHILD_INPUT == child_type)?"input":"output",pid);
    strcat(debuginfo,tmp);

    gotpid = (int)waitpid((pid_t)pid,&status,0);

    my_snprintf(tmp,BUF_SIZE,"got pid %d [%d",gotpid,WIFEXITED(status));
    strcat(debuginfo,tmp);
    if (WIFEXITED(status)) {
      my_snprintf(tmp,BUF_SIZE,"/%d",WEXITSTATUS(status));
      strcat(debuginfo,tmp);
    }
    my_snprintf(tmp,BUF_SIZE,"] [%d",WIFSIGNALED(status));
    strcat(debuginfo,tmp);
    if (WIFSIGNALED(status)) {
      my_snprintf(tmp,BUF_SIZE,"/%d",WTERMSIG(status));
      strcat(debuginfo,tmp);
    }
    my_snprintf(tmp,BUF_SIZE,"] [%d",WIFSTOPPED(status));
    strcat(debuginfo,tmp);
    if (WIFSTOPPED(status)) {
      my_snprintf(tmp,BUF_SIZE,"/%d",WSTOPSIG(status));
      strcat(debuginfo,tmp);
    }
    strcat(debuginfo,"]");

    st_debug(debuginfo);

    if (WIFEXITED(status) && WEXITSTATUS(status)) {
      if (CHILD_OUTPUT == child_type) {
        st_warning("child encoder process %d had non-zero exit status %d",pid,WEXITSTATUS(status));
        retval = CLOSE_CHILD_ERROR_OUTPUT;
      }
      else if (CHILD_INPUT == child_type) {
        retval = CLOSE_CHILD_ERROR_INPUT;
      }
    }
  }

  return retval;
}

int spawn(FILE **readpipe,FILE **writepipe,int child_type,FILE *inputstream)
/* forks off a process running the command cmd, and sets up read/write
 * pipes for two-way communication with that process
 */
{
  int childpid, pipe1[2], pipe2[2];
  int i,nullfd,max_fds;
  char **real_args;
  char tmp[BUF_SIZE],debuginfo[BUF_SIZE];

  if (NULL == (real_args = malloc(num_child_args * (sizeof(char *) + 1)))) {
    perror("shntool: malloc");
    st_error("error while allocating space for child process arguments, see above");
  }

  for (i=0;i<num_child_args;i++)
    real_args[i] = child_args[i];
  real_args[num_child_args] = NULL;

  for (i=0;i<MAX_CHILD_ARGS;i++)
    child_args[i] = NULL;

  arg_reset();

  if ((pipe(pipe1) < 0) || (pipe(pipe2) < 0)) {
    perror("shntool: pipe");
    st_error("error while creating pipes for two-way communication with child process, see above");
   }

  switch ((childpid = fork())) {
    case -1:
      /* fork failed */
      perror("shntool: fork");
      st_error("error while forking child process, see above");
      break;
    case 0:
      /* child */

      close(pipe1[1]);
      close(pipe2[0]);

      /* Read from parent on pipe1[0], write to parent on pipe2[1]. */

      if (inputstream) {
        dup2(fileno(inputstream),0);
        close(fileno(inputstream));
      }
      else {
        dup2(pipe1[0],0);
      }

      dup2(pipe2[1],1);

      close(pipe1[0]);
      close(pipe2[1]);

      /* make sure stderr is connected to /dev/null, or closed if that fails */
      close(2);
      nullfd = open("/dev/null",O_WRONLY);
      if (nullfd > 0 && 2 != nullfd)
        dup2(nullfd,2);

#ifdef __CYGWIN__
      setmode(0,_O_BINARY);
      setmode(1,_O_BINARY);
      setmode(2,_O_BINARY);
#endif

#ifdef HAVE_SYSCONF
      max_fds = sysconf(_SC_OPEN_MAX);
#else
      max_fds = 1024;
#endif

      /* close all other file descriptors */
      for (i=3;i<max_fds;i++)
        close(i);

      if (execvp(real_args[0],real_args) < 0) {
        perror("shntool: execvp");
        st_error("error while executing '%s', see above",real_args[0]);
      }
      break;
    default:
      /* parent */
      close(pipe1[0]);
      close(pipe2[1]);
      /* Write to child on pipe1[1], read from child on pipe2[0]. */
      *readpipe = fdopen(pipe2[0], "r");
      *writepipe = fdopen(pipe1[1], "w");
      setlinebuf(*writepipe);
      SETBINARY_IN(*readpipe);
      SETBINARY_OUT(*writepipe);

      strcpy(debuginfo,"");

      my_snprintf(tmp,BUF_SIZE,"spawned %s process '%s' with pid %d and arguments:",(CHILD_INPUT == child_type)?"input":"output",real_args[0],childpid);
      strcat(debuginfo,tmp);

      for (i=1;real_args[i];i++) {
        my_snprintf(tmp,BUF_SIZE," \"%s\"",real_args[i]);
        strcat(debuginfo,tmp);
      }

      st_debug(debuginfo);

      free(real_args);
      break;
    }

  return childpid;
}

format_module *output_format_init(int argc,char **argv,int *argpos)
{
  int i;
  format_module *op = NULL;

  (*argpos)++;

  if (argc <= *argpos)
    st_help("missing output file format");

  for (i=0;formats[i];i++) {
    if (0 == strcmp(argv[*argpos],formats[i]->name)) {
      if (NULL == formats[i]->output_func)
        st_help("file format '%s' does not support output",formats[i]->name);
      op = formats[i];
      break;
    }
  }

  if (NULL == op)
    st_help("invalid output file format: %s",argv[*argpos]);

  if (op->get_arguments)
    op->get_arguments(argc,argv,argpos);

  return op;
}

void arg_add(char *arg)
{
  if (num_child_args >= MAX_CHILD_ARGS)
    st_error("too many arguments specified - limit is %d",MAX_CHILD_ARGS);

  child_args[num_child_args] = arg;
  num_child_args++;
}

void arg_reset()
{
  num_child_args = 0;
}

void alter_file_order(wave_info **filenames,int numfiles)
/* a simple menu system to change the order of the files as given on the command line */
{
  int i,current,a,b;
  char response[BUF_SIZE],
       *p,
       *args[3];
  wave_info *tmp;

  printf("\nEntering file order editor.\n");

  while (1) {
    printf("\n");
    printf("File list:\n");
    printf("\n");
    for (i=0;i<numfiles;i++)
      printf("%2d: %s\n",i+1,filenames[i]->filename);
    printf("\n");
    printf("Commands:\n");
    printf("\n");
    printf("  swap a b   (swaps positions a and b)\n");
    printf("  move a b   (moves position a to position b, shifting everything in between)\n");
    printf("  begin a    (moves position a to the beginning of the list)\n");
    printf("  end a      (moves position a to the end of the list)\n");
    printf("  done       (quits this editor, and continues with processing)\n");
    printf("  quit       (quits %s [you can also use Ctrl-C])\n",progname);
    printf("\n? ");
    fgets(response,BUF_SIZE-1,stdin);
    response[strlen(response)-1] = '\0';
    p = response;
    current = 0;
    args[0] = NULL;
    args[1] = NULL;
    args[2] = NULL;
    while ('\0' != *p) {
      while (' ' == *p || '\t' == *p || '\n' == *p) {
        *p = '\0';
        p++;
      }
      if ('\0' == *p)
        break;
      if (3 > current)
        args[current++] = p;
      while ('\0' != *p && ' ' != *p && '\t' != *p && '\n' != *p)
        p++;
    }
    if (NULL == args[0])
      args[0] = "";
    if (0 == strcmp(args[0],"quit")) {
      printf("\nQuitting %s.\n",progname);
      exit(1);
    }
    else if (0 == strcmp(args[0],"done")) {
      printf("\nExiting file order editor.\n");
      break;
    }
    else if (0 == strcmp(args[0],"swap")) {
      if (NULL == args[2])
        printf("\nNot enough positions supplied to the 'swap' command\n");
      else {
        a = atoi(args[1]);
        b = atoi(args[2]);
        if ((a<1 || a>numfiles) || (b<1 || b>numfiles))
          printf("\nOne or more of your entries are out of range.  Try again.\n");
        else {
          printf("\nSwapping positions %d and %d...\n",a,b);
          tmp = filenames[a-1];
          filenames[a-1] = filenames[b-1];
          filenames[b-1] = tmp;
        }
      }
    }
    else if (0 == strcmp(args[0],"move")) {
      if (NULL == args[2])
        printf("\nNot enough positions supplied to the 'move' command\n");
      else {
        a = atoi(args[1]);
        b = atoi(args[2]);
        if ((a<1 || a>numfiles) || (b<1 || b>numfiles))
          printf("\nOne or more of your entries are out of range.  Try again.\n");
        else {
          printf("\nMoving position %d to position %d...\n",a,b);
          tmp = filenames[a-1];
          if (a < b) {
            for (i=a-1;i<b-1;i++)
              filenames[i] = filenames[i+1];
          }
          else if (a > b) {
            for (i=a-1;i>b-1;i--)
              filenames[i] = filenames[i-1];
          }
          filenames[b-1] = tmp;
        }
      }
    }
    else if (0 == strcmp(args[0],"begin")) {
      if (NULL == args[1])
        printf("\nMissing a position for the 'begin' command.\n");
      else {
        a = atoi(args[1]);
        if ((a<1 || a>numfiles))
          printf("\nYour entry is out of range.  Try again.\n");
        else {
          printf("\nMoving position %d to the beginning of the list\n",a);
          tmp = filenames[a-1];
          for (i=a-1;i>0;i--)
            filenames[i] = filenames[i-1];
          filenames[0] = tmp;
        }
      }
    }
    else if (0 == strcmp(args[0],"end")) {
      if (NULL == args[1])
        printf("\nMissing a position for the 'end' command.\n");
      else {
        a = atoi(args[1]);
        if ((a<1 || a>numfiles))
          printf("\nYour entry is out of range.  Try again.\n");
        else {
          printf("\nMoving position %d to the end of the list...\n",a);
          tmp = filenames[a-1];
          for (i=a-1;i<numfiles-1;i++)
            filenames[i] = filenames[i+1];
          filenames[numfiles-1] = tmp;
        }
      }
    }
    else {
      printf("\nUnrecognized command: %s\n",args[0]);
    }
  }

  printf("\n");
}

void internal_version()
{
  printf("%s%s %s\n",fullprogname,(NULL == progmode)?"":" mode module",version);
  printf("%s %s\n\n",copyright,author);
  printf("shorten utilities pages:\n\n%s\n",urls);

  printf(
         "\n"
         "This program comes with ABSOLUTELY NO WARRANTY.\n"
         "This is free software, and you are welcome to redistribute it\n"
         "under certain conditions.  See the file COPYING for details.\n"
         "\n"
        );

  exit(0);
}

Generated by  Doxygen 1.6.0   Back to index