/* * * PROGRAM: InterBase server manager * MODULE: ibmgr.cpp * DESCRIPTION: Main routine and parser * * The contents of this file are subject to the Interbase Public * License Version 1.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy * of the License at http://www.Inprise.com/IPL.html * * Software distributed under the License is distributed on an * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express * or implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code was created by Inprise Corporation * and its predecessors. Portions created by Inprise Corporation are * Copyright (C) Inprise Corporation. * * All Rights Reserved. * Contributor(s): ______________________________________. * * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port * */ #include "firebird.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #ifdef HAVE_PWD_H #include #endif #include "../jrd/common.h" #include "../jrd/ibase.h" #include "../utilities/ibmgr/ibmgr.h" #include "../utilities/ibmgr/ibmgrswi.h" #include "../jrd/license.h" #include "../utilities/ibmgr/srvrmgr_proto.h" #include "../common/utils_proto.h" const int MAXARGS = 20; /* max number of args allowed on command line */ const USHORT MAXSTUFF = 1000; /* longest interactive command line */ /* Codes returned by get_switchesi() FB_SUCCESS is defined in common.h */ const SSHORT ERR_SYNTAX = -1; const SSHORT ERR_OTHER = -2; /* Codes returned by parse_cmd_line() FB_SUCCESS is defined in common.h */ const SSHORT ACT_NONE = -1; const SSHORT ACT_QUIT = 1; const SSHORT ACT_PROMPT = 2; static void copy_str_upper(TEXT*, const TEXT*); static bool get_line(int*, SCHAR**, TEXT*); static SSHORT get_switches(int, const TEXT* const*, const in_sw_tab_t*, ibmgr_data_t*, bool*); static SSHORT parse_cmd_line(int, const TEXT* const*); static void print_config(void); static void print_help(void); static ibmgr_data_t ibmgr_data; int CLIB_ROUTINE main( int argc, char **argv) { /************************************** * * m a i n * ************************************** * * Functional description * If there is no command line, prompt for one, read it * and make an artificial argc/argv. Otherwise, pass * the specified argc/argv to IBMGR_exec_line (see below). * **************************************/ #ifdef VMS argc = VMS_parse(&argv, argc); #endif /* Let's see if we have something in environment variables */ Firebird::string user, password; fb_utils::readenv("ISC_USER", user); fb_utils::readenv("ISC_PASSWORD", password); Firebird::string host; /* MMM - do not allow to change host now fb_utils::readenv("ISC_HOST", host); */ TEXT msg[MSG_LEN]; /* Let's get a real user name. This info is used by START server command. Because server is not running we can not check the password, thus we require a real user to be root or FIREBIRD_USER_NAME or INTERBASE_USER_NAME or INTERBASE_USER_SHORT */ const struct passwd* pw = getpwuid(getuid()); if (pw == NULL) { perror("getpwuid"); SRVRMGR_msg_get(MSG_GETPWFAIL, msg); fprintf(OUTFILE, "%s\n", msg); exit(FINI_ERROR); } strcpy(ibmgr_data.real_user, pw->pw_name); if (!strcmp(pw->pw_name, "root") || !strcmp(pw->pw_name, FIREBIRD_USER_NAME) || !strcmp(pw->pw_name, INTERBASE_USER_NAME) || !strcmp(pw->pw_name, INTERBASE_USER_SHORT)) { strcpy(ibmgr_data.user, SYSDBA_USER_NAME); } else copy_str_upper(ibmgr_data.user, pw->pw_name); if (user.length()) copy_str_upper(ibmgr_data.user, user.c_str()); if (password.length()) strcpy(ibmgr_data.password, password.c_str()); else ibmgr_data.password[0] = '\0'; if (host.length()) strcpy(ibmgr_data.host, host.c_str()); else strcpy(ibmgr_data.host, "localhost"); /* Shutdown is not in progress and we are not attached to service yet. But obviously we will need attachment. */ ibmgr_data.shutdown = false; ibmgr_data.attached = 0; ibmgr_data.reattach |= (REA_HOST | REA_USER | REA_PASSWORD); /* No pidfile by default */ ibmgr_data.pidfile[0] = 0; /* Special case a solitary -z switch. Print the version and then drop into prompt mode. */ if (argc == 2 && *argv[1] == '-' && (argv[1][1] == 'Z' || argv[1][1] == 'z')) { parse_cmd_line(argc, argv); argc--; } SSHORT ret; if (argc > 1) { ret = parse_cmd_line(argc, argv); if (ret == FB_SUCCESS) { ret = SRVRMGR_exec_line(&ibmgr_data); if (ret) { SRVRMGR_msg_get(ret, msg); fprintf(OUTFILE, "%s\n", msg); } /* We also need to check the shutdown flag here (if operation was -shut -[noat|notr]) and, depending on what we want to do, either wait here on some sort of a shutdown event, or go to the prompt mode */ SRVRMGR_cleanup(&ibmgr_data); exit(FINI_OK); } else if (ret != ACT_PROMPT) exit(FINI_OK); } int local_argc; SCHAR* local_argv[MAXARGS]; TEXT stuff[MAXSTUFF]; for (;;) { if (get_line(&local_argc, local_argv, stuff)) break; if (local_argc > 1) { ret = parse_cmd_line(local_argc, local_argv); if (ret == ACT_QUIT) break; if (ret == FB_SUCCESS) { ret = SRVRMGR_exec_line(&ibmgr_data); if (ret) { SRVRMGR_msg_get(ret, msg); fprintf(OUTFILE, "%s\n", msg); } } } } SRVRMGR_cleanup(&ibmgr_data); exit(FINI_OK); } static bool get_line( int *argc, SCHAR** argv, TEXT* stuff) { /************************************** * * g e t _ l i n e * ************************************** * * Functional description * Read the current line and put its pieces into an argc/argv * structure. Reads a max of MAXARGS - 1 pieces (argv [0] is * unused), and a max of MAXSTUFF characters, at which point * **************************************/ TEXT msg[MSG_LEN]; SRVRMGR_msg_get(MSG_PROMPT, msg); printf("%s ", msg); /* "FBMGR> " */ /* if (sw_service_gsec) putc ('\001', stdout); */ fflush(stdout); *argc = 1; TEXT* cursor = stuff; USHORT count = MAXSTUFF - 1; bool first = true; /* for each input character, if it's white space (or any non-printable, non-newline for that matter), ignore it; if it's a newline, we're done; otherwise, put it in the current argument */ while (*argc < MAXARGS && count > 0) { TEXT c = getc(stdin); if (c > ' ' && c <= '~') { /* note that the first argument gets a '-' appended to the front to fool the switch checker into thinking it came from the command line */ for (argv[(*argc)++] = cursor; count > 0; count--) { if (first) { first = false; if (c != '?') { *cursor++ = '-'; count--; } } *cursor++ = c; c = getc(stdin); if (c <= ' ' || c > '~') break; } *cursor++ = '\0'; count--; } if (c == '\n') break; if (c == EOF) { if (SYSCALL_INTERRUPTED(errno)) { errno = 0; continue; } else return true; } } *cursor = '\0'; return false; } static SSHORT get_switches( int argc, const TEXT* const* argv, const in_sw_tab_t* in_sw_table, ibmgr_data_t* ibmgr_data, bool * quitflag) { /************************************** * * g e t _ s w i t c h e s * ************************************** * * Functional description * Parse the input line arguments. * Returns: FB_SUCCESS if success * ERR_SYNTAX in case of syntax error. * ERR_OTHER non-syntax error. * **************************************/ TEXT msg[MSG_LEN]; bool sw_version = false; SSHORT err_msg_no; /* Look at each argument. It's either a switch or a parameter. parameters must always follow a switch, but not all switches need parameters. */ *quitflag = false; USHORT last_sw = IN_SW_IBMGR_0; for (--argc; argc > 0; argc--) { const TEXT* string = *++argv; if (*string == '?') ibmgr_data->operation = OP_HELP; else if (*string != '-') { /* this is not a switch, so it must be a parameter for the previous switch, if any */ switch (last_sw) { case IN_SW_IBMGR_POOL: if (strlen(string) >= MAXPATHLEN) { SRVRMGR_msg_get(MSG_FLNMTOOLONG, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } strcpy(ibmgr_data->print_file, string); break; case IN_SW_IBMGR_HOST: /* If the name of the host is the same we are currently attached to, do nothing MMM - it has to be more sophisticated check against compete host name including domain and/or network addreass, but... May be later */ if (strcmp(ibmgr_data->host, string)) { strcpy(ibmgr_data->host, string); ibmgr_data->reattach |= REA_HOST; } break; case IN_SW_IBMGR_PASSWORD: /* If the password is the same, do nothing */ if (strcmp(ibmgr_data->password, string)) { strcpy(ibmgr_data->password, string); ibmgr_data->reattach |= REA_PASSWORD; } break; case IN_SW_IBMGR_USER: /* MMM - we need to compare new entered user with one we already have and do not set the reattach flag if they are the same */ copy_str_upper(ibmgr_data->user, string); if (strlen(ibmgr_data->user) > 127) { SRVRMGR_msg_get(MSG_INVUSER, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } ibmgr_data->reattach |= REA_USER; break; case IN_SW_IBMGR_PIDFILE: { Firebird::PathName pf(string); pf.copyTo(ibmgr_data->pidfile, sizeof(ibmgr_data->pidfile)); break; } case IN_SW_IBMGR_0: SRVRMGR_msg_get(MSG_INVPAR, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; /* The following switches do not take any parameters */ case IN_SW_IBMGR_START: case IN_SW_IBMGR_ONCE: case IN_SW_IBMGR_FOREVER: case IN_SW_IBMGR_SIGNORE: case IN_SW_IBMGR_SHUT: case IN_SW_IBMGR_NOW: case IN_SW_IBMGR_NOAT: case IN_SW_IBMGR_NOTR: case IN_SW_IBMGR_IGNORE: case IN_SW_IBMGR_SET: case IN_SW_IBMGR_SHOW: case IN_SW_IBMGR_QUIT: case IN_SW_IBMGR_HELP: case IN_SW_IBMGR_Z: SRVRMGR_msg_get(MSG_SWNOPAR, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; default: #ifdef DEV_BUILD fprintf(OUTFILE, "ASSERT: file %s line %"LINEFORMAT": last_sw = %d\n", __FILE__, __LINE__, last_sw); #endif ; } last_sw = IN_SW_IBMGR_0; } else { /* iterate through the switch table, looking for matches */ USHORT in_sw = IN_SW_IBMGR_0; const TEXT* q; for (const in_sw_tab_t* in_sw_tab = in_sw_table; q = in_sw_tab->in_sw_name; in_sw_tab++) { const TEXT* p = string + 1; /* handle orphaned hyphen case */ if (!*p--) break; /* compare switch to switch name in table */ int l = 0; while (*p) { if (!*++p) { if (l >= in_sw_tab->in_sw_min_length) in_sw = in_sw_tab->in_sw; else in_sw = IN_SW_IBMGR_AMBIG; } if (UPPER(*p) != *q++) break; l++; } /* end of input means we got a match. stop looking */ if (!*p) break; } /* If the previous switch requires parameters and we are here, this is an error */ switch (last_sw) { case IN_SW_IBMGR_PASSWORD: case IN_SW_IBMGR_USER: case IN_SW_IBMGR_HOST: case IN_SW_IBMGR_POOL: SRVRMGR_msg_get(MSG_REQPAR, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } switch (in_sw) { case IN_SW_IBMGR_START: case IN_SW_IBMGR_SHUT: case IN_SW_IBMGR_SET: case IN_SW_IBMGR_SHOW: case IN_SW_IBMGR_QUIT: case IN_SW_IBMGR_HELP: case IN_SW_IBMGR_PRINT: /* Only one operation can be specified on command line */ if (ibmgr_data->operation) { SRVRMGR_msg_get(MSG_OPSPEC, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } switch (in_sw) { case IN_SW_IBMGR_START: ibmgr_data->operation = OP_START; break; case IN_SW_IBMGR_SHUT: ibmgr_data->operation = OP_SHUT; break; case IN_SW_IBMGR_SET: ibmgr_data->operation = OP_SET; break; case IN_SW_IBMGR_SHOW: ibmgr_data->operation = OP_SHOW; break; case IN_SW_IBMGR_QUIT: ibmgr_data->operation = OP_QUIT; *quitflag = true; break; case IN_SW_IBMGR_HELP: ibmgr_data->operation = OP_HELP; break; case IN_SW_IBMGR_PRINT: ibmgr_data->operation = OP_PRINT; break; } break; case IN_SW_IBMGR_PASSWORD: case IN_SW_IBMGR_USER: case IN_SW_IBMGR_HOST: /* The above switches are separate case: they can be used as operation or parameter switches. If an operation has already been specified, we assume it is a switch else we set an implied operation type OP_SET. We also can not change any the above parameters if a server shutdown is in progress. */ if (ibmgr_data->shutdown) { SRVRMGR_msg_get(MSG_SHUTDOWN, msg); fprintf(OUTFILE, "%s\n", msg); SRVRMGR_msg_get(MSG_CANTCHANGE, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_OTHER; } if (!ibmgr_data->operation) ibmgr_data->operation = OP_SET; /* Check if any of these switches has been already specified. Note that change in any of these psrameters will require new attachment to service */ err_msg_no = 0; switch (in_sw) { case IN_SW_IBMGR_HOST: if (ibmgr_data->par_entered & ENT_HOST) { err_msg_no = MSG_INVSWSW; break; } ibmgr_data->par_entered |= ENT_HOST; break; case IN_SW_IBMGR_PASSWORD: if (ibmgr_data->par_entered & ENT_PASSWORD) { err_msg_no = MSG_INVSWSW; break; } ibmgr_data->par_entered |= ENT_PASSWORD; break; case IN_SW_IBMGR_USER: if (ibmgr_data->par_entered & ENT_USER) { err_msg_no = MSG_INVSWSW; break; } ibmgr_data->par_entered |= ENT_USER; break; } if (err_msg_no) { SRVRMGR_msg_get(err_msg_no, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } break; case IN_SW_IBMGR_ONCE: case IN_SW_IBMGR_FOREVER: case IN_SW_IBMGR_SIGNORE: case IN_SW_IBMGR_NOW: case IN_SW_IBMGR_NOAT: case IN_SW_IBMGR_NOTR: case IN_SW_IBMGR_IGNORE: case IN_SW_IBMGR_POOL: /* These switches are operation modifiers or suboperations. Each of them makes sense only in a contex of certain operation. So, let's make sure it is a right contex. But first let's see if any operation has been specified */ if (!ibmgr_data->operation) { SRVRMGR_msg_get(MSG_NOOPSPEC, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } /* if a suboperation has already been specified get the hell out of here. */ if (ibmgr_data->suboperation) { SRVRMGR_msg_get(MSG_INVSWSW, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } err_msg_no = 0; switch (in_sw) { case IN_SW_IBMGR_ONCE: case IN_SW_IBMGR_FOREVER: case IN_SW_IBMGR_SIGNORE: if (ibmgr_data->operation != OP_START) { err_msg_no = MSG_INVSWOP; break; } switch (in_sw) { case IN_SW_IBMGR_ONCE: ibmgr_data->suboperation = SOP_START_ONCE; break; case IN_SW_IBMGR_SIGNORE: ibmgr_data->suboperation = SOP_START_SIGNORE; break; default: ibmgr_data->suboperation = SOP_START_FOREVER; break; } break; case IN_SW_IBMGR_NOW: case IN_SW_IBMGR_NOAT: case IN_SW_IBMGR_NOTR: case IN_SW_IBMGR_IGNORE: if (ibmgr_data->operation != OP_SHUT) { err_msg_no = MSG_INVSWOP; break; } switch (in_sw) { case IN_SW_IBMGR_NOW: ibmgr_data->suboperation = SOP_SHUT_NOW; break; case IN_SW_IBMGR_NOAT: ibmgr_data->suboperation = SOP_SHUT_NOAT; break; case IN_SW_IBMGR_NOTR: ibmgr_data->suboperation = SOP_SHUT_NOTR; break; default: ibmgr_data->suboperation = SOP_SHUT_IGN; break; } break; case IN_SW_IBMGR_POOL: if (ibmgr_data->operation != OP_PRINT) { err_msg_no = MSG_INVSWOP; break; } ibmgr_data->suboperation = SOP_PRINT_POOL; break; } if (err_msg_no) { SRVRMGR_msg_get(err_msg_no, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } break; case IN_SW_IBMGR_Z: /* This is also a separate case - it could be operation switch or just switch. Also, does not matter how many times version switch was specified, we output version only once */ if (!ibmgr_data->operation) ibmgr_data->operation = OP_VERSION; if (!sw_version) { SRVRMGR_msg_get(MSG_VERSION, msg); fprintf(OUTFILE, "%s %s\n", msg, GDS_VERSION); } break; case IN_SW_IBMGR_0: SRVRMGR_msg_get(MSG_INVSW, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; case IN_SW_IBMGR_AMBIG: SRVRMGR_msg_get(MSG_AMBSW, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; default: #ifdef DEV_BUILD fprintf(OUTFILE, "ASSERT: file %s line %"LINEFORMAT": in_sw = %d\n", __FILE__, __LINE__, in_sw); #endif ; } last_sw = in_sw; } if (*quitflag) break; } /* If the previous switch requires parameters and we are here, this is an error */ switch (last_sw) { case IN_SW_IBMGR_PASSWORD: case IN_SW_IBMGR_USER: case IN_SW_IBMGR_HOST: case IN_SW_IBMGR_PRINT: case IN_SW_IBMGR_POOL: SRVRMGR_msg_get(MSG_REQPAR, msg); fprintf(OUTFILE, "%s\n", msg); return ERR_SYNTAX; } #ifdef DEV_BUILD if (!ibmgr_data->operation) fprintf(OUTFILE, "ASSERT: file %s line %"LINEFORMAT": no operation has been specified\n", __FILE__, __LINE__); #endif return FB_SUCCESS; } static void print_config(void) { /************************************** * * p r i n t _ c o n f i g * ************************************** * * Functional description * **************************************/ fprintf(OUTFILE, "\nHost:\t%s\nUser:\t%s\n\n", ibmgr_data.host, ibmgr_data.user); #ifdef DEBUG fprintf(OUTFILE, "\nReal user:\t%s\nPassword:\t%s\n\n", ibmgr_data.real_user, ibmgr_data.password); fprintf(OUTFILE, "Attached:\t%s\nNew attach:\t%s %s %s\nShutdown:\t%s\n\n", ibmgr_data.attached ? "yes" : "no", ibmgr_data.reattach & REA_HOST ? "HOST" : "no", ibmgr_data.reattach & REA_PASSWORD ? "PASSWORD" : "no", ibmgr_data.reattach & REA_USER ? "USER" : "no", ibmgr_data.shutdown ? "yes" : "no"); #endif } static void print_help(void) { /************************************** * * p r i n t _ h e l p * ************************************** * * Functional description * **************************************/ TEXT msg[MSG_LEN]; TEXT msg2[MSG_LEN]; fprintf(OUTFILE, "\n\n"); fprintf(OUTFILE, "Usage: fbmgr -command [-option [parameter]]\n\n"); fprintf(OUTFILE, "or fbmgr\n"); fprintf(OUTFILE, " FBMGR> command [-option [parameter]]\n\n"); fprintf(OUTFILE, " shut [-now] shutdown server\n"); fprintf(OUTFILE, " show show host and user\n"); fprintf(OUTFILE, " user set user name\n"); fprintf(OUTFILE, " password set DBA password\n"); fprintf(OUTFILE, " pidfile file to save fbserver's PID\n"); fprintf(OUTFILE, " help prints help text\n"); fprintf(OUTFILE, " quit quit prompt mode\n\n"); fprintf(OUTFILE, "Command switches 'user' and 'password' can also be used\n"); fprintf(OUTFILE, "as an option switches for commands like start or shut.\n"); fprintf(OUTFILE, "For example, to shutdown server you can: \n\n"); fprintf(OUTFILE, "fbmgr -shut -password \n\n"); fprintf(OUTFILE, "or\n\n"); fprintf(OUTFILE, "fbmgr\n"); fprintf(OUTFILE, "FBMGR> shut -password \n\n"); fprintf(OUTFILE, "or\n\n"); fprintf(OUTFILE, "fbmgr\n"); fprintf(OUTFILE, "FBMGR> password \n"); fprintf(OUTFILE, "FBMGR> shut\n\n\n"); } static SSHORT parse_cmd_line( int argc, const TEXT* const* argv) { /************************************** * * p a r s e _ c m d _ l i n e * ************************************** * * Functional description * Parses the command line. After it was parsed * executes commands that do require server access * (help, show, etc.) and checks some conditions * for other command. * * returns: * * FB_SUCCESS on normal completion, * ACT_QUIT if user chooses to quit * ACT_PROMT used by main() to switch into * the prompt mode * ACT_NONE on error or * if no further actions necessary * (like help, version, etc.) * **************************************/ TEXT msg[MSG_LEN]; bool quitflag = false; ibmgr_data.operation = OP_NONE; ibmgr_data.suboperation = SOP_NONE; ibmgr_data.par_entered = 0; SSHORT ret = get_switches(argc, argv, ibmgr_in_sw_table, &ibmgr_data, &quitflag); if (ret != FB_SUCCESS) { if (ret == ERR_SYNTAX) { SRVRMGR_msg_get(MSG_SYNTAX, msg); fprintf(OUTFILE, "%s\n", msg); } return ACT_NONE; } switch (ibmgr_data.operation) { case OP_SHUT: if (strcmp(ibmgr_data.user, SYSDBA_USER_NAME)) { SRVRMGR_msg_get(MSG_NOPERM, msg); fprintf(OUTFILE, "%s\n", msg); ret = ACT_NONE; break; } /* The only allowed suboperation when shutdown has been started is to IGNORE it. */ if (ibmgr_data.shutdown && ibmgr_data.suboperation != SOP_SHUT_IGN) { SRVRMGR_msg_get(MSG_SHUTDOWN, msg); fprintf(OUTFILE, "%s\n", msg); SRVRMGR_msg_get(MSG_CANTSHUT, msg); fprintf(OUTFILE, "%s\n", msg); ret = ACT_NONE; } break; case OP_START: if ((strcmp(ibmgr_data.real_user, "root") && strcmp(ibmgr_data.real_user, FIREBIRD_USER_NAME) && strcmp(ibmgr_data.real_user, INTERBASE_USER_NAME) && strcmp(ibmgr_data.real_user, INTERBASE_USER_SHORT)) || strcmp(ibmgr_data.user, SYSDBA_USER_NAME)) { SRVRMGR_msg_get(MSG_NOPERM, msg); fprintf(OUTFILE, "%s\n", msg); ret = ACT_NONE; break; } break; case OP_HELP: print_help(); ret = ACT_NONE; break; case OP_SHOW: print_config(); ret = ACT_NONE; break; case OP_VERSION: ret = ACT_NONE; break; case OP_SET: ret = ACT_PROMPT; break; } if (quitflag) if (ibmgr_data.shutdown) { SRVRMGR_msg_get(MSG_SHUTDOWN, msg); fprintf(OUTFILE, "%s\n", msg); SRVRMGR_msg_get(MSG_CANTQUIT, msg); fprintf(OUTFILE, "%s\n", msg); ret = ACT_NONE; } else ret = ACT_QUIT; return ret; } static void copy_str_upper( TEXT* str1, const TEXT* str2) { /************************************** * * c o p y _ s t r _ u p p e r * ************************************** * * Functional description * Copies string str2 to address str1 * converting str2 to upper case * **************************************/ for (; *str2; str1++, str2++) *str1 = UPPER(*str2); *str1 = '\0'; }