root/libutil/xargs.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. exec_line_limit
  2. repeat_find_read
  3. repeat_find_next
  4. execute_command
  5. xargs_open_generic
  6. xargs_open_with_file
  7. xargs_open_with_argv
  8. xargs_open_with_strbuf
  9. xargs_open_with_find
  10. xargs_read
  11. xargs_unread
  12. xargs_close

/*
 * Copyright (c) 2005 Tama Communications Corporation
 *
 * This file is part of GNU GLOBAL.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

#include "checkalloc.h"
#include "die.h"
#include "env.h"
#include "find.h"
#include "gpathop.h"
#include "locatestring.h"
#include "strbuf.h"
#include "test.h"
#include "xargs.h"

#if !defined(ARG_MAX) && defined(_SC_ARG_MAX)
#define ARG_MAX         sysconf(_SC_ARG_MAX)
#endif

static int exec_line_limit(int);
static char *repeat_find_read(void);
static void repeat_find_next(void);
static FILE *execute_command(XARGS *);
static XARGS *xargs_open_generic(const char *, int);

/*
 * usage: a piece of code to achieve the following command line.
 *
 *      find . -type f -print | xargs grep pattern
 *
 * char *p;
 * FILE *ip = popen("find . -type f -print", "r");
 * XARGS *xp = xargs_open_with_file("grep pattern", ip)
 * xp->ignore_error = 1;
 * while ((p = xargs_read(xp)) != NULL)
 *      puts(p);
 * xargs_close(xp);
 */
/*
 * exec_line_limit: upper limit of bytes of exec line.
 *
 *      i)      length  command line length
 *      r)      0: unknown or cannot afford long line.
 *              > 0: upper limit of exec line
 */
static int
exec_line_limit(int length)
{
        int limit = 0;

#ifdef ARG_MAX
        /*
         * POSIX.2 limits the exec(2) line length to ARG_MAX - 2048.
         */
        limit = ARG_MAX - 2048;
        /*
         * The reason is unknown but the xargs(1) in GNU findutils
         * use this limit.
         */
        if (limit > 20 * 1024)
                limit = 20 * 1024;
        /*
         * Add the command line length.
         * We estimates additional 80 bytes for popen(3).
         *
         * for "/bin/sh -c "                            11bytes
         * reserve                                      69bytes
         * ----------------------------------------------------
         * Total                                        80 bytes
         */
        limit -= length + 80;

        limit -= env_size();
#endif
#if !defined(ARG_MAX) && defined(_WIN32)
        /*
         * The limit lenght of the command line of cmd.exe is 2047
         * characters on Windows 2000 or 8191 characters on Windows XP
         * and later. The 80 below is for safety.
         */
        limit = 2047 - length - 80;
#endif
        if (limit < 0)
                limit = 0;
        return limit;
}
/*
 * repeatable find_read
 */
static char *repeat_lastpath;
static char *
repeat_find_read(void)
{
        if (repeat_lastpath)
                return repeat_lastpath;
        return repeat_lastpath = find_read();
}
static void
repeat_find_next(void)
{
        repeat_lastpath = NULL;
}

/*
 * Specified limitation by user. (e.g. gtags --max-args)
 */
#define LT_MAX ((xp->max_args == 0 || count < xp->max_args))

/*
 * Common processing for each XARGS_XXXX type.
 */
#ifdef _WIN32
#define QUOTE   '"'
#else
#define QUOTE   '\''
#endif
#define APPEND_ARGUMENT(p) {\
        char *path = (p);\
        length = strlen(path);\
        if (*path == ' ') {\
                if (xp->put_gpath && !test("b", ++path))\
                        gpath_put(path, GPATH_OTHER);\
                continue;\
        }\
        if (strbuf_getlen(comline) + length + 2 > limit)\
                break;\
        xp->seqno++;\
        if (xp->put_gpath)\
                gpath_put(path, GPATH_SOURCE);\
        if (xp->skip_assembly && locatestring(path, ".s", MATCH_AT_LAST|IGNORE_CASE) != NULL) {\
                if (xp->verbose)\
                        xp->verbose(path + 2, xp->seqno, 1);\
        } else {\
                if (xp->verbose)\
                        xp->verbose(path + 2, xp->seqno, 0);\
                strbuf_putc(comline, ' ');\
                strbuf_putc(comline, QUOTE);\
                strbuf_puts(comline, path);\
                strbuf_putc(comline, QUOTE);\
                count++;\
        }\
}

/*
 * execute_command
 *
 *      i)      xp      xargs structure
 *      r)              !=NULL: file pointer
 *                      ==NULL: end of argument
 *
 * This function constructs command line from the following,
 *      command: xp->command
 *      argument: each argument provider
 * execute it on a pipe line, and return the file pointer.
 */
static FILE *
execute_command(XARGS *xp)
{
        int limit;
        STRBUF *comline = strbuf_open(0);
        int count = 0;
        int length;
        FILE *pipe = NULL;
        char *p, *meta_p;

        /*
         * Copy the part before '%s' of the command skeleton.
         * The '%s' in the skeleton is replaced with given arguments.
         */
        meta_p = locatestring(xp->command, "%s", MATCH_FIRST);
        if (meta_p) {
                strbuf_nputs(comline, xp->command, meta_p - xp->command);
                limit = exec_line_limit(strlen(meta_p + 2));
        } else {
                strbuf_puts(comline, xp->command);
                limit = exec_line_limit(0);
        }
        /*
         * Append arguments as many as possible.
         */
        switch (xp->type) {
        case XARGS_FILE:
                for (
                        /* initial */
                        fseek(xp->ip, xp->fptr, SEEK_SET)
                        ;
                        /* continuation condition */
                        (LT_MAX &&
                                ((p = (strbuf_getlen(xp->path) > 0 ?
                                strbuf_value(xp->path) :
                                strbuf_fgets(xp->path, xp->ip, STRBUF_NOCRLF))) != NULL))
                        ;
                        /* preparation */
                        strbuf_reset(xp->path)
                )
                        APPEND_ARGUMENT(p);
                xp->fptr = ftell(xp->ip);
                break;
        case XARGS_ARGV:
                for (; LT_MAX && xp->argc > 0; xp->argc--, xp->argv++)
                        APPEND_ARGUMENT(xp->argv[0])
                break;
        case XARGS_STRBUF:
                for (; LT_MAX && xp->curp < xp->endp; xp->curp += length + 1)
                        APPEND_ARGUMENT(xp->curp)
                break;
        case XARGS_FIND:
                for (; LT_MAX && (p = repeat_find_read()) != NULL; repeat_find_next())
                        APPEND_ARGUMENT(p)
                break;
        }
        /*
         * Copy the left part of the command skeleton.
         */
        if (meta_p) {
                strbuf_putc(comline, ' ');
                strbuf_puts(comline, meta_p + 2);
        }
        if (count > 0) {
                pipe = popen(strbuf_value(comline), "r");
                if (pipe == NULL)
                        die("cannot execute command '%s'.", strbuf_value(comline));
        }
        strbuf_close(comline);
        return pipe;
}
/*
 * xargs_open_generic: allocate generic part of xargs structure.
 *
 *      i)      command command line except for the arguments.
 *      i)      max_args 0: no limit, >0: max argument
 *      r)              xargs structure 
 */
static XARGS *
xargs_open_generic(const char *command, int max_args)
{
        XARGS *xp;

        xp  = check_calloc(sizeof(XARGS), 1);
        xp->command = check_strdup(command);
        xp->type = 0;
        xp->pipe = NULL;
        xp->result = strbuf_open(0);
        xp->end_of_arg = 0;
        xp->unread = 0;
        xp->max_args = max_args;
        xp->seqno = 0;
        xp->skip_assembly = 0;
        /*
         * Procedure to display verbose message.
         * proc(path, seqno, skip)
         */
        xp->verbose = NULL;
        /*
         * By default, the error status returned by pclose() is not ignored,
         * because global(1) doesn't return error code when the target
         * was not found.
         * Some commands like grep(1) return error code when the target
         * was not found. If you would like to use such command, set the
         * flag to 1.
         */
        xp->ignore_error = 0;
        /*
         * By default, we doesn't put the path to GPATH.
         * This option is prepared for createtags() and updatetags().
         */
        xp->put_gpath = 0;
        /*
         * By default, we don't cut off the blanks at the tail of the line.
         */
        xp->trim_line = 0;

        return xp;
}
/*
 * xargs_open_with_file: open xargs stream using file
 *
 *      i)      command command skeleton.
 *      i)      max_args 0: no limit, >0: max argument
 *      i)      ip      file pointer
 *      r)              xargs structure
 *
 * The '%s' in the command skeleton is replaced with given arguments.
 * If '%s' doesn't exist, the arguments is appended to the tail of the
 * skeleton.
 */
XARGS *
xargs_open_with_file(const char *command, int max_args, FILE *ip)
{
        XARGS *xp = xargs_open_generic(command, max_args);

        xp->type = XARGS_FILE;
        xp->ip = ip;
        xp->path = strbuf_open(0);
        xp->fptr = 0;
        return xp;
}
/*
 * xargs_open_with_argv: open xargs stream using argv
 *
 *      i)      command command skeleton.
 *      i)      max_args 0: no limit, >0: max argument
 *      i)      argc    argument number
 *      i)      argv    argument array
 *      r)              xargs structure
 *
 * The '%s' in the command skeleton is replaced with given arguments.
 * If '%s' doesn't exist, the arguments is appended to the tail of the
 * skeleton.
 */
XARGS *
xargs_open_with_argv(const char *command, int max_args, int argc, char **argv)
{
        XARGS *xp = xargs_open_generic(command, max_args);

        xp->type = XARGS_ARGV;
        xp->argc = argc;
        xp->argv = argv;
        return xp;
}
/*
 * xargs_open_with_strbuf: open xargs stream using string buffer
 *
 *      i)      command command skeleton.
 *      i)      max_args 0: no limit, >0: max argument
 *      i)      sb      string buffer
 *      r)              xargs structure
 *
 * The '%s' in the command skeleton is replaced with given arguments.
 * If '%s' doesn't exist, the arguments is appended to the tail of the
 * skeleton.
 */
XARGS *
xargs_open_with_strbuf(const char *command, int max_args, STRBUF *sb)
{
        XARGS *xp = xargs_open_generic(command, max_args);

        xp->type = XARGS_STRBUF;
        xp->curp = strbuf_value(sb);
        xp->endp = xp->curp + strbuf_getlen(sb);
        return xp;
}
/*
 * xargs_open_with_find: open xargs stream using find().
 *
 *      i)      command command skeleton.
 *      i)      max_args 0: no limit, >0: max argument
 *      r)              xargs structure
 *
 * The '%s' in the command skeleton is replaced with given arguments.
 * If '%s' doesn't exist, the arguments is appended to the tail of the
 * skeleton.
 */
XARGS *
xargs_open_with_find(const char *command, int max_args)
{
        XARGS *xp = xargs_open_generic(command, max_args);

        xp->type = XARGS_FIND;
        return xp;
}
/*
 * xargs_read: read a record from xargs stream
 *
 *      i)      xp      xargs structure
 *      r)              result line
 */
char *
xargs_read(XARGS *xp)
{
        assert(xp != NULL);
        if (xp->end_of_arg)
                return NULL;
        if (xp->unread) {
                xp->unread = 0;
                return strbuf_value(xp->result);
        }
        if (xp->pipe && strbuf_fgets(xp->result, xp->pipe, STRBUF_NOCRLF) != NULL) {
                if (xp->trim_line)
                        strbuf_trim(xp->result);
                return strbuf_value(xp->result);
        }
        if (xp->pipe)
                if (pclose(xp->pipe) != 0 && !xp->ignore_error)
                        die("command failed in xargs_read().");
        /*
         * Switch to the next segment.
         */
        do {
                xp->pipe = execute_command(xp);
                if (xp->pipe && strbuf_fgets(xp->result, xp->pipe, STRBUF_NOCRLF) != NULL) {
                        if (xp->trim_line)
                                strbuf_trim(xp->result);
                        return strbuf_value(xp->result);
                }
                if (xp->pipe) {
                        if (pclose(xp->pipe) != 0 && !xp->ignore_error)
                                die("command failed in xargs_read().");
                } else {
                        xp->end_of_arg = 1;
                }
        } while (!xp->end_of_arg);

        return NULL;
}
/*
 * xargs_unread: push back a record to xargs stream
 *
 *      i)      xp      xargs structure
 */
void
xargs_unread(XARGS *xp)
{
        assert(xp != NULL);
        xp->unread = 1;
}
/*
 * xargs_close(xp)
 *
 *      i)      xp      xargs structure
 */
int
xargs_close(XARGS *xp)
{
        int count;

        assert(xp != NULL);
        count = xp->seqno;
        assert(xp->pipe == NULL);
        free(xp->command);
        strbuf_close(xp->result);

        switch (xp->type) {
        case XARGS_FILE:
                strbuf_close(xp->path);
                break;
        case XARGS_ARGV:
        case XARGS_STRBUF:
                /* Nothing to do */
                break;
        }
        free(xp);
        return count;
}

/* [<][>][^][v][top][bottom][index][help] */