示例#1
0
/* if fn is NULL then input is stdin and output is stdout */
static void convert(char *fn, int conv_type)
{
	FILE *in, *out;
	int ch;
	char *temp_fn = temp_fn; /* for compiler */
	char *resolved_fn = resolved_fn;

	in = stdin;
	out = stdout;
	if (fn != NULL) {
		struct stat st;
		int fd;

		resolved_fn = xmalloc_follow_symlinks(fn);
		if (resolved_fn == NULL)
			bb_simple_perror_msg_and_die(fn);
		in = xfopen_for_read(resolved_fn);
		xfstat(fileno(in), &st, resolved_fn);

		temp_fn = xasprintf("%sXXXXXX", resolved_fn);
		fd = xmkstemp(temp_fn);
		if (fchmod(fd, st.st_mode) == -1)
			bb_simple_perror_msg_and_die(temp_fn);
		fchown(fd, st.st_uid, st.st_gid);

		out = xfdopen_for_write(fd);
	}

	while ((ch = fgetc(in)) != EOF) {
		if (ch == '\r')
			continue;
		if (ch == '\n')
			if (conv_type == CT_UNIX2DOS)
				fputc('\r', out);
		fputc(ch, out);
	}

	if (fn != NULL) {
		if (fclose(in) < 0 || fclose(out) < 0) {
			unlink(temp_fn);
			bb_perror_nomsg_and_die();
		}
		xrename(temp_fn, resolved_fn);
		free(temp_fn);
		free(resolved_fn);
	}
}
示例#2
0
文件: sed.c 项目: rehsack/busybox
int sed_main(int argc UNUSED_PARAM, char **argv)
{
	unsigned opt;
	llist_t *opt_e, *opt_f;
	char *opt_i;

#if ENABLE_LONG_OPTS
	static const char sed_longopts[] ALIGN1 =
		/* name             has_arg             short */
		"in-place\0"        Optional_argument   "i"
		"regexp-extended\0" No_argument         "r"
		"quiet\0"           No_argument         "n"
		"silent\0"          No_argument         "n"
		"expression\0"      Required_argument   "e"
		"file\0"            Required_argument   "f";
#endif

	INIT_G();

	/* destroy command strings on exit */
	if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);

	/* Lie to autoconf when it starts asking stupid questions. */
	if (argv[1] && strcmp(argv[1], "--version") == 0) {
		puts("This is not GNU sed version 4.0");
		return 0;
	}

	/* do normal option parsing */
	opt_e = opt_f = NULL;
	opt_i = NULL;
	/* -i must be first, to match OPT_in_place definition */
	/* -E is a synonym of -r:
	 * GNU sed 4.2.1 mentions it in neither --help
	 * nor manpage, but does recognize it.
	 */
	opt = getopt32long(argv, "^"
			"i::rEne:*f:*"
			"\0" "nn"/*count -n*/,
			sed_longopts,
			&opt_i, &opt_e, &opt_f,
			&G.be_quiet); /* counter for -n */
	//argc -= optind;
	argv += optind;
	if (opt & OPT_in_place) { // -i
		die_func = cleanup_outname;
	}
	if (opt & (2|4))
		G.regex_type |= REG_EXTENDED; // -r or -E
	//if (opt & 8)
	//	G.be_quiet++; // -n (implemented with a counter instead)
	while (opt_e) { // -e
		add_cmd_block(llist_pop(&opt_e));
	}
	while (opt_f) { // -f
		char *line;
		FILE *cmdfile;
		cmdfile = xfopen_stdin(llist_pop(&opt_f));
		while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
			add_cmd(line);
			free(line);
		}
		fclose_if_not_stdin(cmdfile);
	}
	/* if we didn't get a pattern from -e or -f, use argv[0] */
	if (!(opt & 0x30)) {
		if (!*argv)
			bb_show_usage();
		add_cmd_block(*argv++);
	}
	/* Flush any unfinished commands. */
	add_cmd("");

	/* By default, we write to stdout */
	G.nonstdout = stdout;

	/* argv[0..(argc-1)] should be names of file to process. If no
	 * files were specified or '-' was specified, take input from stdin.
	 * Otherwise, we process all the files specified. */
	G.input_file_list = argv;
	if (!argv[0]) {
		if (opt & OPT_in_place)
			bb_error_msg_and_die(bb_msg_requires_arg, "-i");
		argv[0] = (char*)bb_msg_standard_input;
		/* G.last_input_file = 0; - already is */
	} else {
		goto start;

		for (; *argv; argv++) {
			struct stat statbuf;
			int nonstdoutfd;
			sed_cmd_t *sed_cmd;

			G.last_input_file++;
 start:
			if (!(opt & OPT_in_place)) {
				if (LONE_DASH(*argv)) {
					*argv = (char*)bb_msg_standard_input;
					process_files();
				}
				continue;
			}

			/* -i: process each FILE separately: */

			if (stat(*argv, &statbuf) != 0) {
				bb_simple_perror_msg(*argv);
				G.exitcode = EXIT_FAILURE;
				G.current_input_file++;
				continue;
			}
			G.outname = xasprintf("%sXXXXXX", *argv);
			nonstdoutfd = xmkstemp(G.outname);
			G.nonstdout = xfdopen_for_write(nonstdoutfd);
			/* Set permissions/owner of output file */
			/* chmod'ing AFTER chown would preserve suid/sgid bits,
			 * but GNU sed 4.2.1 does not preserve them either */
			fchmod(nonstdoutfd, statbuf.st_mode);
			fchown(nonstdoutfd, statbuf.st_uid, statbuf.st_gid);

			process_files();
			fclose(G.nonstdout);
			G.nonstdout = stdout;

			if (opt_i) {
				char *backupname = xasprintf("%s%s", *argv, opt_i);
				xrename(*argv, backupname);
				free(backupname);
			}
			/* else unlink(*argv); - rename below does this */
			xrename(G.outname, *argv); //TODO: rollback backup on error?
			free(G.outname);
			G.outname = NULL;

			/* Fix disabled range matches and mangled ",+N" ranges */
			for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
				sed_cmd->beg_line = sed_cmd->beg_line_orig;
				sed_cmd->end_line = sed_cmd->end_line_orig;
			}
		}
		/* Here, to handle "sed 'cmds' nonexistent_file" case we did:
		 * if (G.current_input_file[G.current_input_file] == NULL)
		 *	return G.exitcode;
		 * but it's not needed since process_files() works correctly
		 * in this case too. */
	}

	process_files();

	return G.exitcode;
}
示例#3
0
static int parse(const char *boundary, char **argv)
{
	char *line, *s, *p;
	const char *type;
	int boundary_len = strlen(boundary);
	const char *delims = " ;\"\t\r\n";
	const char *uniq;
	int ntokens;
	const char *tokens[32]; // 32 is enough

	// prepare unique string pattern
	uniq = xasprintf("%%llu.%u.%s", (unsigned)getpid(), safe_gethostname());

//bb_info_msg("PARSE[%s]", uniq);

	while ((line = xmalloc_fgets_str(stdin, "\r\n\r\n")) != NULL) {

		// seek to start of MIME section
		// N.B. to avoid false positives let us seek to the _last_ occurance
		p = NULL;
		s = line;
		while ((s = strcasestr(s, "Content-Type:")) != NULL)
			p = s++;
		if (!p)
			goto next;
//bb_info_msg("L[%s]", p);

		// split to tokens
		// TODO: strip of comments which are of form: (comment-text)
		ntokens = 0;
		tokens[ntokens] = NULL;
		for (s = strtok(p, delims); s; s = strtok(NULL, delims)) {
			tokens[ntokens] = s;
			if (ntokens < ARRAY_SIZE(tokens) - 1)
				ntokens++;
//bb_info_msg("L[%d][%s]", ntokens, s);
		}
		tokens[ntokens] = NULL;
//bb_info_msg("N[%d]", ntokens);

		// analyse tokens
		type = find_token(tokens, "Content-Type:", "text/plain");
//bb_info_msg("T[%s]", type);
		if (0 == strncasecmp(type, "multipart/", 10)) {
			if (0 == strcasecmp(type+10, "mixed")) {
				parse(xfind_token(tokens, "boundary="), argv);
			} else
				bb_error_msg_and_die("no support of content type '%s'", type);
		} else {
			pid_t pid = pid;
			int rc;
			FILE *fp;
			// fetch charset
			const char *charset = find_token(tokens, "charset=", CONFIG_FEATURE_MIME_CHARSET);
			// fetch encoding
			const char *encoding = find_token(tokens, "Content-Transfer-Encoding:", "7bit");
			// compose target filename
			char *filename = (char *)find_token(tokens, "filename=", NULL);
			if (!filename)
				filename = xasprintf(uniq, monotonic_us());
			else
				filename = bb_get_last_path_component_strip(xstrdup(filename));

			// start external helper, if any
			if (opts & OPT_X) {
				int fd[2];
				xpipe(fd);
				pid = vfork();
				if (0 == pid) {
					// child reads from fd[0]
					close(fd[1]);
					xmove_fd(fd[0], STDIN_FILENO);
					xsetenv("CONTENT_TYPE", type);
					xsetenv("CHARSET", charset);
					xsetenv("ENCODING", encoding);
					xsetenv("FILENAME", filename);
					BB_EXECVP_or_die(argv);
				}
				// parent dumps to fd[1]
				close(fd[0]);
				fp = xfdopen_for_write(fd[1]);
				signal(SIGPIPE, SIG_IGN); // ignore EPIPE
			// or create a file for dump
			} else {
				char *fname = xasprintf("%s%s", *argv, filename);
				fp = xfopen_for_write(fname);
				free(fname);
			}

			// housekeeping
			free(filename);

			// dump to fp
			if (0 == strcasecmp(encoding, "base64")) {
				read_base64(stdin, fp, '-');
			} else if (0 != strcasecmp(encoding, "7bit")
				&& 0 != strcasecmp(encoding, "8bit")
			) {
				// quoted-printable, binary, user-defined are unsupported so far
				bb_error_msg_and_die("no support of encoding '%s'", encoding);
			} else {
				// N.B. we have written redundant \n. so truncate the file
				// The following weird 2-tacts reading technique is due to
				// we have to not write extra \n at the end of the file
				// In case of -x option we could truncate the resulting file as
				// fseek(fp, -1, SEEK_END);
				// if (ftruncate(fileno(fp), ftell(fp)))
				//	bb_perror_msg("ftruncate");
				// But in case of -X we have to be much more careful. There is
				// no means to truncate what we already have sent to the helper.
				p = xmalloc_fgets_str(stdin, "\r\n");
				while (p) {
					s = xmalloc_fgets_str(stdin, "\r\n");
					if (s == NULL)
						break;
					if ('-' == s[0]
					 && '-' == s[1]
					 && 0 == strncmp(s+2, boundary, boundary_len)
					) {
						break;
					}
					fputs(p, fp);
					p = s;
				}

/*
				while ((s = xmalloc_fgetline_str(stdin, "\r\n")) != NULL) {
					if ('-' == s[0] && '-' == s[1]
						&& 0 == strncmp(s+2, boundary, boundary_len))
						break;
					fprintf(fp, "%s\n", s);
				}
				// N.B. we have written redundant \n. so truncate the file
				fseek(fp, -1, SEEK_END);
				if (ftruncate(fileno(fp), ftell(fp)))
					bb_perror_msg("ftruncate");
*/
			}
			fclose(fp);

			// finalize helper
			if (opts & OPT_X) {
				signal(SIGPIPE, SIG_DFL);
				// exit if helper exited >0
				rc = (wait4pid(pid) & 0xff);
				if (rc)
					return rc+20;
			}

			// check multipart finalized
			if (s && '-' == s[2+boundary_len] && '-' == s[2+boundary_len+1]) {
				free(line);
				break;
			}
		}
 next:
		free(line);
	}

//bb_info_msg("ENDPARSE[%s]", boundary);

	return EXIT_SUCCESS;
}
示例#4
0
文件: sed.c 项目: pgavin/or1k-busybox
int sed_main(int argc UNUSED_PARAM, char **argv)
{
	unsigned opt;
	llist_t *opt_e, *opt_f;
	char *opt_i;

#if ENABLE_LONG_OPTS
	static const char sed_longopts[] ALIGN1 =
		/* name             has_arg             short */
		"in-place\0"        Optional_argument   "i"
		"regexp-extended\0" No_argument         "r"
		"quiet\0"           No_argument         "n"
		"silent\0"          No_argument         "n"
		"expression\0"      Required_argument   "e"
		"file\0"            Required_argument   "f";
#endif

	int status = EXIT_SUCCESS;

	INIT_G();

	/* destroy command strings on exit */
	if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);

	/* Lie to autoconf when it starts asking stupid questions. */
	if (argv[1] && strcmp(argv[1], "--version") == 0) {
		puts("This is not GNU sed version 4.0");
		return 0;
	}

	/* do normal option parsing */
	opt_e = opt_f = NULL;
	opt_i = NULL;
	opt_complementary = "e::f::" /* can occur multiple times */
	                    "nn"; /* count -n */

	IF_LONG_OPTS(applet_long_options = sed_longopts);

	/* -i must be first, to match OPT_in_place definition */
	opt = getopt32(argv, "i::rne:f:", &opt_i, &opt_e, &opt_f,
			    &G.be_quiet); /* counter for -n */
	//argc -= optind;
	argv += optind;
	if (opt & OPT_in_place) { // -i
		atexit(cleanup_outname);
	}
	if (opt & 0x2) G.regex_type |= REG_EXTENDED; // -r
	//if (opt & 0x4) G.be_quiet++; // -n
	while (opt_e) { // -e
		add_cmd_block(llist_pop(&opt_e));
	}
	while (opt_f) { // -f
		char *line;
		FILE *cmdfile;
		cmdfile = xfopen_for_read(llist_pop(&opt_f));
		while ((line = xmalloc_fgetline(cmdfile)) != NULL) {
			add_cmd(line);
			free(line);
		}
		fclose(cmdfile);
	}
	/* if we didn't get a pattern from -e or -f, use argv[0] */
	if (!(opt & 0x18)) {
		if (!*argv)
			bb_show_usage();
		add_cmd_block(*argv++);
	}
	/* Flush any unfinished commands. */
	add_cmd("");

	/* By default, we write to stdout */
	G.nonstdout = stdout;

	/* argv[0..(argc-1)] should be names of file to process. If no
	 * files were specified or '-' was specified, take input from stdin.
	 * Otherwise, we process all the files specified. */
	if (argv[0] == NULL) {
		if (opt & OPT_in_place)
			bb_error_msg_and_die(bb_msg_requires_arg, "-i");
		add_input_file(stdin);
	} else {
		int i;

		for (i = 0; argv[i]; i++) {
			struct stat statbuf;
			int nonstdoutfd;
			FILE *file;
			sed_cmd_t *sed_cmd;

			if (LONE_DASH(argv[i]) && !(opt & OPT_in_place)) {
				add_input_file(stdin);
				process_files();
				continue;
			}
			file = fopen_or_warn(argv[i], "r");
			if (!file) {
				status = EXIT_FAILURE;
				continue;
			}
			add_input_file(file);
			if (!(opt & OPT_in_place)) {
				continue;
			}

			/* -i: process each FILE separately: */

			G.outname = xasprintf("%sXXXXXX", argv[i]);
			nonstdoutfd = xmkstemp(G.outname);
			G.nonstdout = xfdopen_for_write(nonstdoutfd);

			/* Set permissions/owner of output file */
			fstat(fileno(file), &statbuf);
			/* chmod'ing AFTER chown would preserve suid/sgid bits,
			 * but GNU sed 4.2.1 does not preserve them either */
			fchmod(nonstdoutfd, statbuf.st_mode);
			fchown(nonstdoutfd, statbuf.st_uid, statbuf.st_gid);

			process_files();
			fclose(G.nonstdout);
			G.nonstdout = stdout;

			if (opt_i) {
				char *backupname = xasprintf("%s%s", argv[i], opt_i);
				xrename(argv[i], backupname);
				free(backupname);
			}
			/* else unlink(argv[i]); - rename below does this */
			xrename(G.outname, argv[i]); //TODO: rollback backup on error?
			free(G.outname);
			G.outname = NULL;

			/* Re-enable disabled range matches */
			for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) {
				sed_cmd->beg_line = sed_cmd->beg_line_orig;
			}
		}
		/* Here, to handle "sed 'cmds' nonexistent_file" case we did:
		 * if (G.current_input_file >= G.input_file_count)
		 *	return status;
		 * but it's not needed since process_files() works correctly
		 * in this case too. */
	}
	process_files();

	return status;
}
示例#5
0
/*
 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
    only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser

 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
    only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup

 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
    only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
    like in addgroup and member != NULL

 4) delete a user: update_passwd(FILE, USER, NULL, NULL)

 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)

 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
    only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL

 7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL)
    only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
    or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd

 8) delete a user from all groups: update_passwd(FILE, NULL, NULL, MEMBER)

 This function does not validate the arguments fed to it
 so the calling program should take care of that.

 Returns number of lines changed, or -1 on error.
*/
int FAST_FUNC update_passwd(const char *filename,
		const char *name,
		const char *new_passwd,
		const char *member)
{
#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
#define member NULL
#endif
	struct stat sb;
	struct flock lock;
	FILE *old_fp;
	FILE *new_fp;
	char *fnamesfx;
	char *sfx_char;
	char *name_colon;
	int old_fd;
	int new_fd;
	int i;
	int changed_lines;
	int ret = -1; /* failure */
	/* used as a bool: "are we modifying /etc/shadow?" */
#if ENABLE_FEATURE_SHADOWPASSWDS
	const char *shadow = strstr(filename, "shadow");
#else
# define shadow NULL
#endif

	filename = xmalloc_follow_symlinks(filename);
	if (filename == NULL)
		return ret;

	if (name)
		check_selinux_update_passwd(name);

	/* New passwd file, "/etc/passwd+" for now */
	fnamesfx = xasprintf("%s+", filename);
	sfx_char = &fnamesfx[strlen(fnamesfx)-1];
	name_colon = xasprintf("%s:", name ? name : "");

	if (shadow)
		old_fp = fopen(filename, "r+");
	else
		old_fp = fopen_or_warn(filename, "r+");
	if (!old_fp) {
		if (shadow)
			ret = 0; /* missing shadow is not an error */
		goto free_mem;
	}
	old_fd = fileno(old_fp);

	selinux_preserve_fcontext(old_fd);

	/* Try to create "/etc/passwd+". Wait if it exists. */
	i = 30;
	do {
		// FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
		new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
		if (new_fd >= 0) goto created;
		if (errno != EEXIST) break;
		usleep(100000); /* 0.1 sec */
	} while (--i);
	bb_perror_msg("can't create '%s'", fnamesfx);
	goto close_old_fp;

 created:
	if (fstat(old_fd, &sb) == 0) {
		fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
		fchown(new_fd, sb.st_uid, sb.st_gid);
	}
	errno = 0;
	new_fp = xfdopen_for_write(new_fd);

	/* Backup file is "/etc/passwd-" */
	*sfx_char = '-';
	/* Delete old backup */
	i = (unlink(fnamesfx) && errno != ENOENT);
	/* Create backup as a hardlink to current */
	if (i || link(filename, fnamesfx))
		bb_perror_msg("warning: can't create backup copy '%s'",
				fnamesfx);
	*sfx_char = '+';

	/* Lock the password file before updating */
	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	if (fcntl(old_fd, F_SETLK, &lock) < 0)
		bb_perror_msg("warning: can't lock '%s'", filename);
	lock.l_type = F_UNLCK;

	/* Read current password file, write updated /etc/passwd+ */
	changed_lines = 0;
	while (1) {
		char *cp, *line;

		line = xmalloc_fgetline(old_fp);
		if (!line) /* EOF/error */
			break;

		if (!name && member) {
			/* Delete member from all groups */
			/* line is "GROUP:PASSWD:[member1[,member2]...]" */
			unsigned member_len = strlen(member);
			char *list = strrchr(line, ':');
			while (list) {
				list++;
 next_list_element:
				if (is_prefixed_with(list, member)) {
					char c;
					changed_lines++;
					c = list[member_len];
					if (c == '\0') {
						if (list[-1] == ',')
							list--;
						*list = '\0';
						break;
					}
					if (c == ',') {
						overlapping_strcpy(list, list + member_len + 1);
						goto next_list_element;
					}
					changed_lines--;
				}
				list = strchr(list, ',');
			}
			fprintf(new_fp, "%s\n", line);
			goto next;
		}

		cp = is_prefixed_with(line, name_colon);
		if (!cp) {
			fprintf(new_fp, "%s\n", line);
			goto next;
		}

		/* We have a match with "name:"... */
		/* cp points past "name:" */

#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
		if (member) {
			/* It's actually /etc/group+, not /etc/passwd+ */
			if (ENABLE_FEATURE_ADDUSER_TO_GROUP
			 && applet_name[0] == 'a'
			) {
				/* Add user to group */
				fprintf(new_fp, "%s%s%s\n", line,
					last_char_is(line, ':') ? "" : ",",
					member);
				changed_lines++;
			} else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
			/* && applet_name[0] == 'd' */
			) {
				/* Delete user from group */
				char *tmp;
				const char *fmt = "%s";

				/* find the start of the member list: last ':' */
				cp = strrchr(line, ':');
				/* cut it */
				*cp++ = '\0';
				/* write the cut line name:passwd:gid:
				 * or name:!:: */
				fprintf(new_fp, "%s:", line);
				/* parse the tokens of the member list */
				tmp = cp;
				while ((cp = strsep(&tmp, ",")) != NULL) {
					if (strcmp(member, cp) != 0) {
						fprintf(new_fp, fmt, cp);
						fmt = ",%s";
					} else {
						/* found member, skip it */
						changed_lines++;
					}
				}
				fprintf(new_fp, "\n");
			}
		} else
#endif
		if ((ENABLE_PASSWD && applet_name[0] == 'p')
		 || (ENABLE_CHPASSWD && applet_name[0] == 'c')
		) {
			/* Change passwd */
			cp = strchrnul(cp, ':'); /* move past old passwd */

			if (shadow && *cp == ':') {
				/* /etc/shadow's field 3 (passwd change date) needs updating */
				/* move past old change date */
				cp = strchrnul(cp + 1, ':');
				/* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
				fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
					(unsigned)(time(NULL)) / (24*60*60), cp);
			} else {
				/* "name:" + "new_passwd" + ":rest of line" */
				fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
			}
			changed_lines++;
		} /* else delete user or group: skip the line */
 next:
		free(line);
	}

	if (changed_lines == 0) {
#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
		if (member) {
			if (ENABLE_ADDGROUP && applet_name[0] == 'a')
				bb_error_msg("can't find %s in %s", name, filename);
			if (ENABLE_DELGROUP && applet_name[0] == 'd')
				bb_error_msg("can't find %s in %s", member, filename);
		}
#endif
		if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
		 && applet_name[0] == 'a' && !member
		) {
			/* add user or group */
			fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
			changed_lines++;
		}
	}

	fcntl(old_fd, F_SETLK, &lock);

	/* We do want all of them to execute, thus | instead of || */
	errno = 0;
	if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
	 || rename(fnamesfx, filename)
	) {
		/* At least one of those failed */
		bb_perror_nomsg();
		goto unlink_new;
	}
	/* Success: ret >= 0 */
	ret = changed_lines;

 unlink_new:
	if (ret < 0)
		unlink(fnamesfx);

 close_old_fp:
	fclose(old_fp);

 free_mem:
	free(fnamesfx);
	free((char *)filename);
	free(name_colon);
	return ret;
}