1/* see LICENSE file for copyright and license details */
2#include <sys/stat.h>
3#include <sys/types.h>
4
5#include <errno.h>
6#include <fcntl.h>
7#include <grp.h>
8#include <libgen.h>
9#include <limits.h>
10#include <pwd.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <unistd.h>
15
16#include "fs.h"
17#include "util.h"
18#include "wexec.h"
19
20static int dflag, Dflag, pflag, sflag;
21static mode_t mode = 0755;
22static uid_t uid = (uid_t)-1;
23static gid_t gid = (gid_t)-1;
24static int status;
25
26static void
27usage(void)
28{
29 eprintf(
30 "usage: %s [-Dps] [-o owner] [-g group] [-m mode] [-t dir] source ... dest\n"
31 " %s -d [-Dp] [-o owner] [-g group] [-m mode] dir ...\n",
32 argv0,
33 argv0
34 );
35}
36
37static void
38lookup_owner(const char *owner, const char *group)
39{
40 struct passwd *pw;
41 struct group *gr;
42
43 if (owner) {
44 errno = 0;
45 pw = getpwnam(owner);
46 if (pw) {
47 uid = pw->pw_uid;
48 } else {
49 if (errno)
50 eprintf("getpwnam %s:", owner);
51 uid = (uid_t)estrtonum(owner, 0, UINT_MAX);
52 }
53 }
54 if (group) {
55 errno = 0;
56 gr = getgrnam(group);
57 if (gr) {
58 gid = gr->gr_gid;
59 } else {
60 if (errno)
61 eprintf("getgrnam %s:", group);
62 gid = (gid_t)estrtonum(group, 0, UINT_MAX);
63 }
64 }
65}
66
67static void
68chown_if_requested(const char *path)
69{
70 if (uid == (uid_t)-1 && gid == (gid_t)-1)
71 return;
72 if (chown(path, uid, gid) < 0) {
73 weprintf("chown %s:", path);
74 status = 1;
75 }
76}
77
78static void
79preserve_time(const char *src, const char *dst)
80{
81 struct stat st;
82 struct timespec times[2];
83
84 if (stat(src, &st) < 0) {
85 weprintf("stat %s:", src);
86 status = 1;
87 return;
88 }
89 times[0] = st.st_atim;
90 times[1] = st.st_mtim;
91 if (utimensat(AT_FDCWD, dst, times, 0) < 0) {
92 weprintf("utimensat %s:", dst);
93 status = 1;
94 }
95}
96
97static void
98strip_binary(const char *path)
99{
100 char *args[] = {"strip", "-p", (char *)path, NULL};
101
102 if (wexecvp("strip", args) != 0) {
103 weprintf("strip %s:", path);
104 status = 1;
105 }
106}
107
108// leading path components of dst, not dst itself
109static void
110make_leading_dirs(const char *dst)
111{
112 char buf[PATH_MAX];
113 char *dir;
114
115 estrlcpy(buf, dst, sizeof(buf));
116 dir = dirname(buf);
117 if (mkdirp(dir, 0755, 0755) < 0 && errno != EEXIST) {
118 weprintf("mkdir %s:", dir);
119 status = 1;
120 }
121}
122
123static void
124install_dirs(char **argv)
125{
126 for (; *argv; argv++) {
127 if (mkdirp(*argv, mode, 0755) < 0 && errno != EEXIST) {
128 weprintf("mkdir %s:", *argv);
129 status = 1;
130 continue;
131 }
132 if (chmod(*argv, mode) < 0) {
133 weprintf("chmod %s:", *argv);
134 status = 1;
135 }
136 chown_if_requested(*argv);
137 }
138}
139
140static void
141install_one(const char *src, const char *dst)
142{
143 if (Dflag)
144 make_leading_dirs(dst);
145
146 cp_fflag = 1;
147 cp_pflag = 0;
148 cp_rflag = 0;
149 cp_follow = 'L';
150 cp(src, dst, 0);
151 if (cp_status) {
152 status = 1;
153 return;
154 }
155
156 if (sflag)
157 strip_binary(dst);
158
159 if (chmod(dst, mode) < 0) {
160 weprintf("chmod %s:", dst);
161 status = 1;
162 }
163 chown_if_requested(dst);
164 if (pflag)
165 preserve_time(src, dst);
166}
167
168// ?man install: copy files and set attributes
169// ?man arguments: source ... dest
170// ?man install copies each source to dest, or into dest when dest is a
171// ?man directory or -t is given, then applies mode/owner/group; with
172// ?man -d, every argument is a directory to create instead
173int
174main(int argc, char *argv[])
175{
176 const char *owner = NULL, *group = NULL, *modestr = NULL, *targetdir = NULL;
177 const char *dest;
178 struct stat st;
179 int isdir;
180 int i;
181
182 ARGBEGIN
183 {
184 // ?man -c: ignored; accepted for compatibility with older install callers
185 case 'c':
186 break;
187 // ?man -d: create directories instead of installing files
188 case 'd':
189 dflag = 1;
190 break;
191 // ?man -D: create dest's leading directories before installing
192 case 'D':
193 Dflag = 1;
194 break;
195 // ?man -p: preserve source modification and access times
196 case 'p':
197 pflag = 1;
198 break;
199 // ?man -s: strip the installed file's symbol table
200 case 's':
201 sflag = 1;
202 break;
203 // ?man -o:owner set the installed file's owner (name or uid)
204 case 'o':
205 owner = EARGF(usage());
206 break;
207 // ?man -g:group set the installed file's group (name or gid)
208 case 'g':
209 group = EARGF(usage());
210 break;
211 // ?man -m:mode set the installed file's permissions
212 case 'm':
213 modestr = EARGF(usage());
214 break;
215 // ?man -t:dir install every source into dir
216 case 't':
217 targetdir = EARGF(usage());
218 break;
219 default:
220 usage();
221 }
222 ARGEND;
223
224 if (argc < 1 || (dflag && (targetdir || sflag)))
225 usage();
226 if (modestr)
227 mode = parsemode(modestr, 0, 0);
228 lookup_owner(owner, group);
229
230 if (dflag) {
231 install_dirs(argv);
232 goto shut;
233 }
234
235 if (targetdir) {
236 isdir = 1;
237 dest = targetdir;
238 if (Dflag && mkdirp(targetdir, 0755, 0755) < 0 && errno != EEXIST) {
239 weprintf("mkdir %s:", targetdir);
240 status = 1;
241 }
242 } else {
243 if (argc < 2)
244 usage();
245 dest = argv[--argc];
246 isdir = stat(dest, &st) == 0 && S_ISDIR(st.st_mode);
247 if (!isdir && argc > 1)
248 eprintf("%s: not a directory\n", dest);
249 }
250
251 for (i = 0; i < argc; i++) {
252 if (isdir) {
253 char buf[PATH_MAX], nb[PATH_MAX];
254 const char *base;
255
256 estrlcpy(nb, argv[i], sizeof(nb));
257 base = basename(nb);
258 estrlcpy(buf, dest, sizeof(buf));
259 estrlcat(buf, "/", sizeof(buf));
260 estrlcat(buf, base, sizeof(buf));
261 install_one(argv[i], buf);
262 } else {
263 install_one(argv[i], dest);
264 }
265 }
266
267shut:
268 return fshut(stdout, "<stdout>") || status;
269}