1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* miscinit.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* miscellanious initialization support stuff
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
* Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2000-01-18 06:10:29 +01:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.40 2000/01/18 05:10:29 ishii Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
2000-01-13 19:26:18 +01:00
|
|
|
#include "postgres.h"
|
|
|
|
|
1999-07-16 05:14:30 +02:00
|
|
|
#include <sys/param.h>
|
1996-07-09 08:22:35 +02:00
|
|
|
#include <sys/types.h>
|
2000-01-09 13:15:57 +01:00
|
|
|
#include <signal.h>
|
1996-07-09 08:22:35 +02:00
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/file.h>
|
1996-11-06 11:32:10 +01:00
|
|
|
#include <unistd.h>
|
1999-07-16 05:14:30 +02:00
|
|
|
#include <grp.h>
|
|
|
|
#include <pwd.h>
|
2000-01-13 19:26:18 +01:00
|
|
|
#include <stdlib.h>
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
#include "catalog/catname.h"
|
1998-02-25 14:09:49 +01:00
|
|
|
#include "catalog/pg_shadow.h"
|
1999-07-16 07:23:30 +02:00
|
|
|
#include "miscadmin.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
|
|
|
1998-02-24 16:27:04 +01:00
|
|
|
#ifdef CYR_RECODE
|
|
|
|
unsigned char RecodeForwTable[128];
|
|
|
|
unsigned char RecodeBackTable[128];
|
1998-10-08 20:30:52 +02:00
|
|
|
#endif
|
|
|
|
|
2000-01-13 19:26:18 +01:00
|
|
|
ProcessingMode Mode = InitProcessing;
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
1997-09-07 07:04:48 +02:00
|
|
|
* database path / name support stuff
|
1996-07-09 08:22:35 +02:00
|
|
|
* ----------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
2000-01-13 19:26:18 +01:00
|
|
|
SetDatabasePath(const char *path)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2000-01-13 19:26:18 +01:00
|
|
|
free(DatabasePath);
|
|
|
|
/* use strdup since this is done before memory contexts are set up */
|
|
|
|
if (path)
|
|
|
|
{
|
|
|
|
DatabasePath = strdup(path);
|
|
|
|
AssertState(DatabasePath);
|
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2000-01-13 19:26:18 +01:00
|
|
|
SetDatabaseName(const char *name)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2000-01-13 19:26:18 +01:00
|
|
|
free(DatabaseName);
|
|
|
|
if (name)
|
|
|
|
{
|
|
|
|
DatabaseName = strdup(name);
|
|
|
|
AssertState(DatabaseName);
|
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
1998-07-26 06:31:41 +02:00
|
|
|
#ifndef MULTIBYTE
|
|
|
|
/* even if MULTIBYTE is not enabled, this function is neccesary
|
2000-01-18 06:10:29 +01:00
|
|
|
* since pg_proc.h has entries for them.
|
1998-07-24 05:32:46 +02:00
|
|
|
*/
|
|
|
|
const char *
|
|
|
|
getdatabaseencoding()
|
|
|
|
{
|
2000-01-18 06:10:29 +01:00
|
|
|
elog(ERROR, "MultiByte support must be enabled to use this function");
|
|
|
|
return ("");
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *pg_encoding_to_char(int encoding)
|
|
|
|
{
|
|
|
|
elog(ERROR, "MultiByte support must be enabled to use this function");
|
|
|
|
return ("");
|
|
|
|
}
|
|
|
|
|
|
|
|
int pg_char_to_encoding(const char *encoding_string)
|
|
|
|
{
|
|
|
|
elog(ERROR, "MultiByte support must be enabled to use this function");
|
1998-09-01 06:40:42 +02:00
|
|
|
return ("");
|
1998-07-24 05:32:46 +02:00
|
|
|
}
|
1998-09-01 06:40:42 +02:00
|
|
|
|
1998-07-24 05:32:46 +02:00
|
|
|
#endif
|
|
|
|
|
1998-02-24 16:27:04 +01:00
|
|
|
#ifdef CYR_RECODE
|
1998-02-26 05:46:47 +01:00
|
|
|
#define MAX_TOKEN 80
|
1998-02-24 16:27:04 +01:00
|
|
|
|
|
|
|
/* Some standard C libraries, including GNU, have an isblank() function.
|
|
|
|
Others, including Solaris, do not. So we have our own.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
isblank(const char c)
|
|
|
|
{
|
1998-09-01 05:29:17 +02:00
|
|
|
return c == ' ' || c == 9 /* tab */ ;
|
1998-02-24 16:27:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
next_token(FILE *fp, char *buf, const int bufsz)
|
|
|
|
{
|
|
|
|
/*--------------------------------------------------------------------------
|
|
|
|
Grab one token out of fp. Tokens are strings of non-blank
|
|
|
|
characters bounded by blank characters, beginning of line, and end
|
|
|
|
of line. Blank means space or tab. Return the token as *buf.
|
|
|
|
Leave file positioned to character immediately after the token or
|
|
|
|
EOF, whichever comes first. If no more tokens on line, return null
|
|
|
|
string as *buf and position file to beginning of next line or EOF,
|
|
|
|
whichever comes first.
|
|
|
|
--------------------------------------------------------------------------*/
|
|
|
|
int c;
|
|
|
|
char *eb = buf + (bufsz - 1);
|
|
|
|
|
|
|
|
/* Move over inital token-delimiting blanks */
|
|
|
|
while (isblank(c = getc(fp)));
|
|
|
|
|
|
|
|
if (c != '\n')
|
|
|
|
{
|
|
|
|
|
|
|
|
/*
|
|
|
|
* build a token in buf of next characters up to EOF, eol, or
|
|
|
|
* blank.
|
|
|
|
*/
|
|
|
|
while (c != EOF && c != '\n' && !isblank(c))
|
|
|
|
{
|
|
|
|
if (buf < eb)
|
|
|
|
*buf++ = c;
|
|
|
|
c = getc(fp);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put back the char right after the token (putting back EOF
|
|
|
|
* is ok)
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
ungetc(c, fp);
|
|
|
|
}
|
|
|
|
*buf = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
read_through_eol(FILE *file)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
|
|
|
|
do
|
|
|
|
c = getc(file);
|
|
|
|
while (c != '\n' && c != EOF);
|
|
|
|
}
|
|
|
|
|
1998-02-26 05:46:47 +01:00
|
|
|
void
|
|
|
|
SetCharSet()
|
1998-02-24 16:27:04 +01:00
|
|
|
{
|
1998-02-26 05:46:47 +01:00
|
|
|
FILE *file;
|
|
|
|
char *p,
|
|
|
|
c,
|
|
|
|
eof = false;
|
|
|
|
char *map_file;
|
|
|
|
char buf[MAX_TOKEN];
|
|
|
|
int i;
|
|
|
|
unsigned char FromChar,
|
|
|
|
ToChar;
|
|
|
|
|
|
|
|
for (i = 0; i < 128; i++)
|
|
|
|
{
|
|
|
|
RecodeForwTable[i] = i + 128;
|
|
|
|
RecodeBackTable[i] = i + 128;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = getenv("PG_RECODETABLE");
|
|
|
|
if (p && *p != '\0')
|
|
|
|
{
|
|
|
|
map_file = (char *) malloc((strlen(DataDir) +
|
|
|
|
strlen(p) + 2) * sizeof(char));
|
|
|
|
sprintf(map_file, "%s/%s", DataDir, p);
|
1999-01-17 07:20:06 +01:00
|
|
|
#ifndef __CYGWIN32__
|
1999-05-09 02:54:30 +02:00
|
|
|
file = AllocateFile(map_file, "r");
|
1999-01-17 07:20:06 +01:00
|
|
|
#else
|
1999-05-09 02:54:30 +02:00
|
|
|
file = AllocateFile(map_file, "rb");
|
1999-01-17 07:20:06 +01:00
|
|
|
#endif
|
1998-02-26 05:46:47 +01:00
|
|
|
if (file == NULL)
|
|
|
|
return;
|
|
|
|
eof = false;
|
|
|
|
while (!eof)
|
|
|
|
{
|
|
|
|
c = getc(file);
|
|
|
|
ungetc(c, file);
|
|
|
|
if (c == EOF)
|
|
|
|
eof = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (c == '#')
|
|
|
|
read_through_eol(file);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Read the FromChar */
|
|
|
|
next_token(file, buf, sizeof(buf));
|
|
|
|
if (buf[0] != '\0')
|
|
|
|
{
|
|
|
|
FromChar = strtoul(buf, 0, 0);
|
|
|
|
/* Read the ToChar */
|
|
|
|
next_token(file, buf, sizeof(buf));
|
|
|
|
if (buf[0] != '\0')
|
|
|
|
{
|
|
|
|
ToChar = strtoul(buf, 0, 0);
|
|
|
|
RecodeForwTable[FromChar - 128] = ToChar;
|
|
|
|
RecodeBackTable[ToChar - 128] = FromChar;
|
|
|
|
}
|
|
|
|
read_through_eol(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
1999-05-09 02:54:30 +02:00
|
|
|
FreeFile(file);
|
1998-02-26 05:46:47 +01:00
|
|
|
free(map_file);
|
|
|
|
}
|
1998-02-24 16:27:04 +01:00
|
|
|
}
|
|
|
|
|
1998-02-26 05:46:47 +01:00
|
|
|
char *
|
|
|
|
convertstr(unsigned char *buff, int len, int dest)
|
1998-02-24 16:27:04 +01:00
|
|
|
{
|
1998-02-26 05:46:47 +01:00
|
|
|
int i;
|
|
|
|
char *ch = buff;
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++, buff++)
|
|
|
|
{
|
|
|
|
if (*buff > 127)
|
|
|
|
if (dest)
|
|
|
|
*buff = RecodeForwTable[*buff - 128];
|
|
|
|
else
|
|
|
|
*buff = RecodeBackTable[*buff - 128];
|
|
|
|
}
|
|
|
|
return ch;
|
1998-02-24 16:27:04 +01:00
|
|
|
}
|
1998-02-26 05:46:47 +01:00
|
|
|
|
1998-02-24 16:27:04 +01:00
|
|
|
#endif
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/* ----------------
|
1997-09-07 07:04:48 +02:00
|
|
|
* GetPgUserName and SetPgUserName
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* SetPgUserName must be called before InitPostgres, since the setuid()
|
|
|
|
* is done there.
|
1997-04-27 21:21:06 +02:00
|
|
|
*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Replace GetPgUserName() with a lower-case version
|
|
|
|
* to allow use in new case-insensitive SQL (referenced
|
|
|
|
* in pg_proc.h). Define GetPgUserName() as a macro - tgl 97/04/26
|
1996-07-09 08:22:35 +02:00
|
|
|
* ----------------
|
|
|
|
*/
|
1998-02-26 05:46:47 +01:00
|
|
|
char *
|
1997-04-27 21:21:06 +02:00
|
|
|
getpgusername()
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-07 07:04:48 +02:00
|
|
|
return UserName;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SetPgUserName()
|
|
|
|
{
|
|
|
|
#ifndef NO_SECURITY
|
1997-09-08 04:41:22 +02:00
|
|
|
char *p;
|
|
|
|
struct passwd *pw;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
if (IsUnderPostmaster)
|
|
|
|
{
|
|
|
|
/* use the (possibly) authenticated name that's provided */
|
|
|
|
if (!(p = getenv("PG_USER")))
|
1998-10-05 04:48:49 +02:00
|
|
|
elog(FATAL, "SetPgUserName: PG_USER environment variable is unset");
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* setuid() has not yet been done, see above comment */
|
|
|
|
if (!(pw = getpwuid(geteuid())))
|
1998-10-05 04:48:49 +02:00
|
|
|
elog(FATAL, "SetPgUserName: no entry in host passwd file");
|
1997-09-07 07:04:48 +02:00
|
|
|
p = pw->pw_name;
|
|
|
|
}
|
|
|
|
if (UserName)
|
|
|
|
free(UserName);
|
|
|
|
UserName = malloc(strlen(p) + 1);
|
|
|
|
strcpy(UserName, p);
|
1998-09-01 06:40:42 +02:00
|
|
|
#endif /* NO_SECURITY */
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
1997-09-07 07:04:48 +02:00
|
|
|
* GetUserId and SetUserId
|
1996-07-09 08:22:35 +02:00
|
|
|
* ----------------------------------------------------------------
|
|
|
|
*/
|
1997-09-08 04:41:22 +02:00
|
|
|
static Oid UserId = InvalidOid;
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1998-08-11 20:28:49 +02:00
|
|
|
int
|
1996-07-09 08:22:35 +02:00
|
|
|
GetUserId()
|
|
|
|
{
|
2000-01-13 19:26:18 +01:00
|
|
|
AssertState(OidIsValid(UserId));
|
1998-09-01 05:29:17 +02:00
|
|
|
return UserId;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
SetUserId()
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
HeapTuple userTup;
|
|
|
|
char *userName;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-01-13 19:26:18 +01:00
|
|
|
AssertState(!OidIsValid(UserId));/* only once */
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't do scans if we're bootstrapping, none of the system catalogs
|
|
|
|
* exist yet, and they should be owned by postgres anyway.
|
|
|
|
*/
|
|
|
|
if (IsBootstrapProcessingMode())
|
|
|
|
{
|
|
|
|
UserId = geteuid();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
userName = GetPgUserName();
|
1999-11-24 17:52:50 +01:00
|
|
|
userTup = SearchSysCacheTuple(SHADOWNAME,
|
1998-09-01 06:40:42 +02:00
|
|
|
PointerGetDatum(userName),
|
|
|
|
0, 0, 0);
|
1997-09-07 07:04:48 +02:00
|
|
|
if (!HeapTupleIsValid(userTup))
|
1998-10-05 04:48:49 +02:00
|
|
|
elog(FATAL, "SetUserId: user '%s' is not in '%s'",
|
1997-09-07 07:04:48 +02:00
|
|
|
userName,
|
1998-02-25 14:09:49 +01:00
|
|
|
ShadowRelationName);
|
|
|
|
UserId = (Oid) ((Form_pg_shadow) GETSTRUCT(userTup))->usesysid;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
2000-01-09 13:15:57 +01:00
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* posmaster pid file stuffs. $DATADIR/postmaster.pid is created when:
|
|
|
|
*
|
|
|
|
* (1) postmaster starts. In this case pid > 0.
|
|
|
|
* (2) postgres starts in standalone mode. In this case
|
|
|
|
* pid < 0
|
|
|
|
*
|
|
|
|
* to gain an interlock.
|
|
|
|
*
|
|
|
|
* SetPidFname(datadir)
|
|
|
|
* Remember the the pid file name. This is neccesary
|
|
|
|
* UnlinkPidFile() is called from proc_exit().
|
|
|
|
*
|
|
|
|
* GetPidFname(datadir)
|
|
|
|
* Get the pid file name. SetPidFname() should be called
|
|
|
|
* before GetPidFname() gets called.
|
|
|
|
*
|
|
|
|
* UnlinkPidFile()
|
|
|
|
* This is called from proc_exit() and unlink the pid file.
|
|
|
|
*
|
|
|
|
* SetPidFile(pid_t pid)
|
|
|
|
* Create the pid file. On failure, it checks if the process
|
|
|
|
* actually exists or not. SetPidFname() should be called
|
|
|
|
* in prior to calling SetPidFile().
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Path to pid file. proc_exit() remember it to unlink the file.
|
|
|
|
*/
|
|
|
|
static char PidFile[MAXPGPATH];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove the pid file. This function is called from proc_exit.
|
|
|
|
*/
|
|
|
|
void UnlinkPidFile(void)
|
|
|
|
{
|
|
|
|
unlink(PidFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set path to the pid file
|
|
|
|
*/
|
|
|
|
void SetPidFname(char * datadir)
|
|
|
|
{
|
|
|
|
snprintf(PidFile, sizeof(PidFile), "%s/%s", datadir, PIDFNAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get path to the pid file
|
|
|
|
*/
|
|
|
|
char *GetPidFname(void)
|
|
|
|
{
|
|
|
|
return(PidFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the pid file
|
|
|
|
*/
|
|
|
|
int SetPidFile(pid_t pid)
|
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
char *pidfile;
|
|
|
|
char pidstr[32];
|
|
|
|
int len;
|
|
|
|
pid_t post_pid;
|
|
|
|
int is_postgres = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Creating pid file
|
|
|
|
*/
|
|
|
|
pidfile = GetPidFname();
|
|
|
|
fd = open(pidfile, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
|
|
if (fd < 0) {
|
|
|
|
/*
|
|
|
|
* Couldn't create the pid file. Probably
|
|
|
|
* it already exists. Read the file to see if the process
|
|
|
|
* actually exists
|
|
|
|
*/
|
|
|
|
fd = open(pidfile, O_RDONLY, 0600);
|
|
|
|
if (fd < 0) {
|
|
|
|
fprintf(stderr, "Can't open pid file: %s\n", pidfile);
|
|
|
|
fprintf(stderr, "Please check the permission and try again.\n");
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
if ((len = read(fd, pidstr, sizeof(pidstr)-1)) < 0) {
|
|
|
|
fprintf(stderr, "Can't read pid file: %s\n", pidfile);
|
|
|
|
fprintf(stderr, "Please check the permission and try again.\n");
|
|
|
|
close(fd);
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check to see if the process actually exists
|
|
|
|
*/
|
|
|
|
pidstr[len] = '\0';
|
|
|
|
post_pid = (pid_t)atoi(pidstr);
|
|
|
|
|
|
|
|
/* if pid < 0, the pid is for postgres, not postmatser */
|
|
|
|
if (post_pid < 0) {
|
|
|
|
is_postgres++;
|
|
|
|
post_pid = -post_pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post_pid == 0 || (post_pid > 0 && kill(post_pid, 0) < 0)) {
|
|
|
|
/*
|
|
|
|
* No, the process did not exist. Unlink
|
|
|
|
* the file and try to create it
|
|
|
|
*/
|
|
|
|
if (unlink(pidfile) < 0) {
|
|
|
|
fprintf(stderr, "Can't remove pid file: %s\n", pidfile);
|
|
|
|
fprintf(stderr, "The file seems accidently left, but I couldn't remove it.\n");
|
|
|
|
fprintf(stderr, "Please remove the file by hand and try again.\n");
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
fd = open(pidfile, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
|
|
if (fd < 0) {
|
|
|
|
fprintf(stderr, "Can't create pid file: %s\n", pidfile);
|
|
|
|
fprintf(stderr, "Please check the permission and try again.\n");
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Another postmaster is running
|
|
|
|
*/
|
|
|
|
fprintf(stderr, "Can't create pid file: %s\n", pidfile);
|
|
|
|
if (is_postgres) {
|
|
|
|
fprintf(stderr, "Is another postgres (pid: %d) running?\n", post_pid);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Is another postmaster (pid: %s) running?\n", pidstr);
|
|
|
|
}
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sprintf(pidstr, "%d", pid);
|
|
|
|
if (write(fd, pidstr, strlen(pidstr)) != strlen(pidstr)) {
|
|
|
|
fprintf(stderr,"Write to pid file failed\n");
|
|
|
|
fprintf(stderr, "Please check the permission and try again.\n");
|
|
|
|
close(fd);
|
|
|
|
unlink(pidfile);
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
return(0);
|
|
|
|
}
|