Here is a slightly different approach using data.table which creates the totals before reshaping from long to wide format:
library(data.table)
setDT(dt)[, .(entry = min(year), exit = max(year)), by = firm][, All := .N, by = entry][
, dcast(.SD, entry + All ~ exit, length, value.var = "firm")]
entry All 2002 2004 2005
1: 2001 3 2 1 0
2: 2002 3 1 1 1
This is already conveying all essential results which the OP verbally described in the question.
However, OP's expected result includes columns for the years 2001 and 2003 although they contain only 0. If it is required to show years without entries or exits, this can be accomplished by completing the missing years before computing the totals All and reshaping:
setDT(dt)[, .(entry = min(year), exit = max(year)), by = firm][
CJ(entry = dt$year, exit = dt$year, unique = TRUE), on = .(entry, exit)][
, All := sum(!is.na(firm)), by = entry][][
, dcast(.SD, entry + All ~ exit, function(x) (sum(!is.na(x))), value.var = "firm")]
entry All 2001 2002 2003 2004 2005
1: 2001 3 0 2 0 1 0
2: 2002 3 0 1 0 1 1
3: 2003 0 0 0 0 0 0
4: 2004 0 0 0 0 0 0
5: 2005 0 0 0 0 0 0
The missing years are completed by joining with a table of all available combinations of year which is created by a cross join CJ(). The completion introduces a lot of NA values in firm, therefore length(firm) has to be replaced by sum(!is.na(firm)) as aggregation function.
The extension of the resulting wide format can be controlled by the range of years given in CJ(). E.g., the empty entry years 2003 to 2005 can be dropped by
setDT(dt)[, .(entry = min(year), exit = max(year)), by = firm][
CJ(entry = min(entry):max(entry), exit = dt$year, unique = TRUE), on = .(entry, exit)][
, All := sum(!is.na(firm)), by = entry][][
, dcast(.SD, entry + All ~ exit, function(x) (sum(!is.na(x))), value.var = "firm")]
entry All 2001 2002 2003 2004 2005
1: 2001 3 0 2 0 1 0
2: 2002 3 0 1 0 1 1
which reproduces exactly OP's expected resulttable.
Or, drop also the empty exit years 2001 and 2005 by
setDT(dt)[, .(entry = min(year), exit = max(year)), by = firm][
CJ(entry = min(entry):max(entry), exit = min(exit):max(exit)), on = .(entry, exit)][
, All := sum(!is.na(firm)), by = entry][][
, dcast(.SD, entry + All ~ exit, function(x) (sum(!is.na(x))), value.var = "firm")]
entry All 2002 2003 2004 2005
1: 2001 3 2 0 1 0
2: 2002 3 1 0 1 1