update changelog
[alioth/cvs.git] / src / suck.c
1 /*-
2  * Copyright (c) 2011
3  *      Thorsten Glaser <tg@mirbsd.org>
4  *
5  * Provided that these terms and disclaimer and all copyright notices
6  * are retained or reproduced in an accompanying document, permission
7  * is granted to deal in this work without restriction, including un-
8  * limited rights to use, publicly perform, distribute, sell, modify,
9  * merge, give away, or sublicence.
10  *
11  * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
12  * the utmost extent permitted by applicable law, neither express nor
13  * implied; without malicious intent or gross negligence. In no event
14  * may a licensor, author or contributor be held liable for indirect,
15  * direct, other damage, loss, or other issues arising in any way out
16  * of dealing in the work, even if advised of the possibility of such
17  * damage or existence of a defect, except proven that it results out
18  * of said person's immediate fault when using the work as intended.
19  *-
20  * Download a ,v file from the repository
21  */
22
23 #include "cvs.h"
24
25 #ifdef HAVE_MMAP
26 #include <sys/mman.h>
27
28 #ifndef MAP_FILE
29 #define MAP_FILE 0
30 #endif
31
32 #ifndef MAP_FAILED
33 #define MAP_FAILED ((void *)-1)
34 #endif
35 #endif
36
37 static const char * const suck_usage[] = {
38         "Usage: %s %s module/filename\n",
39         NULL
40 };
41
42 int
43 suck(int argc, char *argv[])
44 {
45         size_t m, n;
46         int fd;
47         char *buf, *cp, *fn;
48         struct stat sb;
49         FILE *fp;
50         RCSNode *rcs;
51
52         if (argc != 2)
53                 usage(suck_usage);
54
55 #ifdef CLIENT_SUPPORT
56         if (current_parsed_root->isremote) {
57                 start_server();
58
59                 if (!supported_request("suck"))
60                         error(1, 0, "server does not support %s", "suck");
61
62                 send_arg(argv[1]);
63                 send_to_server("suck\012", 0);
64
65                 return (get_responses_and_close());
66         }
67 #endif
68
69         /* check for ../ attack */
70         if (pathname_levels(argv[1]) > 0)
71                 error(1, 0, "path %s outside of repository", argv[1]);
72
73         /* repo + / + module/file */
74         cp = Xasprintf("%s/%s", current_parsed_root->directory, argv[1]);
75
76         /* find the slash */
77         if ((fn = cp + (last_component(cp) - cp)) == cp)
78                 usage(suck_usage);
79
80         /* repo/module + file */
81         fn[-1] = '\0';
82
83         /* check if it's a valid RCS file, not /etc/passwd or somesuch */
84         if ((rcs = RCS_parse(fn, cp)) == NULL) {
85                 error(1, 0, "not a valid RCS file: %s/%s", cp, fn);
86                 return (1);
87         }
88
89         /* save the real pathname of the RCS file for later */
90         fn = xstrdup(rcs->path);
91
92         /* free up resources allocated until now */
93         freercsnode(&rcs);
94         free(cp);
95
96         /* attempt to open the file ourselves */
97         if ((fp = CVS_FOPEN(fn, FOPEN_BINARY_READ)) == NULL)
98                 error(1, errno, "Could not open RCS archive %s", fn);
99         if (fstat(fd = fileno(fp), &sb) < 0)
100                 error(1, errno, "Could not stat RCS archive %s", fn);
101
102         /*XXX this code will fail for large files */
103
104         /* attempt to slurp entire file into memory */
105 #ifdef HAVE_MMAP
106         buf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
107         if (buf == NULL || buf == MAP_FAILED) {
108                 error(0, errno, "Could not map memory to RCS archive %s", fn);
109 #endif
110         /* backup: just read */
111                 cp = buf = xmalloc(n = sb.st_size);
112                 while (n) {
113                         m = read(fd, cp, n);
114                         if (m == (size_t)-1)
115                                 error(1, errno,
116                                     "Could not read RCS archive %s", fn);
117                         cp += m;
118                         n -= m;
119                 }
120 #ifdef HAVE_MMAP
121         }
122 #endif
123
124         /* write real pathname plus newline as text */
125         cvs_output(fn + strlen(current_parsed_root->directory) + 1, 0);
126         cvs_output("\n", 1);
127
128         /* write file content as binary */
129         cvs_output_binary(buf, sb.st_size);
130
131         /* release all resources allocated */
132 #ifdef HAVE_MMAP
133         munmap(buf, sb.st_size);
134 #endif
135         fclose(fp);
136         free(fn);
137
138         /* success */
139         return (0);
140 }